Skip to content
Learni
View all tutorials
Blockchain

How to Set Up Hardhat for Ethereum in 2026

Lire en français

Introduction

In 2026, Hardhat remains the gold standard for developing, testing, and deploying Ethereum smart contracts. Unlike the more rigid Truffle, Hardhat offers unmatched flexibility with its plugins, built-in REPL, and native chain forking support. This intermediate tutorial walks you through creating a full project step by step: from installation to deploying on Sepolia (Ethereum testnet).

Why Hardhat? It speeds up debugging with detailed traces, integrates Mocha/Chai for robust tests, and handles multi-network deployments via Ethers.js. Think of it like a mechanic's workshop: all the tools at hand to assemble a Solidity engine without friction. By the end, you'll have a working Greeter contract that's tested and deployed. Ready to dive into Web3? (142 words)

Prerequisites

  • Node.js 20+ and npm/yarn/pnpm
  • Basic Solidity knowledge (variables, functions, events)
  • MetaMask account with Sepolia ETH (from a faucet)
  • Git for version control
  • Editor like VS Code with Solidity extension

Initialize the Hardhat Project

terminal
mkdir hardhat-greeter && cd hardhat-greeter
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-verify
npx hardhat init
# Select 'Create a JavaScript project' then 'Yes, with a sample project'

These commands create a project folder, initialize package.json, and install Hardhat with its toolbox (Solidity compiler, tests, ethers.js, verification). The init generates a boilerplate structure: contracts/, scripts/, test/, hardhat.config.js. Avoid global Hardhat versions to isolate dependencies.

Understanding the Generated Structure

After init, your project includes:

  • contracts/: Solidity smart contracts.
  • scripts/: JS scripts for deployment.
  • test/: Unit tests with Mocha/Chai.
  • hardhat.config.js: Central config (networks, Solidity version).

It's like a prefab house: ready to customize. We'll replace the sample with a custom Greeter.

Configure hardhat.config.js

hardhat.config.js
require('@nomicfoundation/hardhat-toolbox');

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: {
    version: '0.8.28',
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },
  networks: {
    hardhat: {},
    sepolia: {
      url: 'https://sepolia.infura.io/v3/VOTRE_CLE_INFURA',
      accounts: ['0xVOTRE_PRIVATE_KEY']
    }
  },
  etherscan: {
    apiKey: {
      sepolia: 'VOTRE_CLE_ETHERSCAN'
    }
  }
};

This config sets Solidity 0.8.28 with optimization (reduces gas costs). Adds Sepolia via Infura (replace with your keys). The 'hardhat' network simulates a local blockchain for testing. Swap placeholders for real keys (free Infura/Etherscan). Pitfall: Forgetting the optimizer hikes production gas costs.

Create the Greeter Smart Contract

contracts/Greeter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract Greeter {
    string private _greeting;

    constructor(string memory greeting) {
        _greeting = greeting;
    }

    function greet() public view returns (string memory) {
        return _greeting;
    }

    function setGreeting(string memory greeting) public {
        _greeting = greeting;
    }
}

This contract stores a private message, exposes a 'greet()' getter, and a modifiable setter. Uses ^0.8.28 for security (built-in SafeMath). Constructor initializes it. Real-world example: Deploy with 'Hello Hardhat!' and update to 'Hello 2026'. Copy-paste directly.

Compile the Contract

Hardhat auto-compiles .sol files into artifacts (ABI + bytecode). Run npx hardhat compile to verify. Output goes to artifacts/ and cache/. Analogy: Like a C compiler to machine code, but for the EVM.

Write Unit Tests

test/Greeter.js
const { expect } = require('chai');
const { ethers } = require('hardhat');

describe('Greeter', function () {
  it('Should return the greeting provided in constructor', async function () {
    const Greeter = await ethers.getContractFactory('Greeter');
    const greeter = await Greeter.deploy('Hello Hardhat!');
    await greeter.waitForDeployment();

    expect(await greeter.greet()).to.equal('Hello Hardhat!');
  });

  it('Should emit event on setGreeting', async function () {
    const Greeter = await ethers.getContractFactory('Greeter');
    const greeter = await Greeter.deploy('Hello Hardhat!');
    await greeter.waitForDeployment();
    await expect(greeter.setGreeting('Hola 2026'))
      .to.emit(greeter, 'GreetingUpdated') // Add event if needed
      .to.not.be.reverted;
  });
});

Tests verify the constructor and setter using Chai expect. Uses ethers for factory/deploy. Second test checks non-revert. Run with npx hardhat test. Add a GreetingUpdated event for full coverage. Pitfall: Skipping waitForDeployment() causes timeouts.

Deployment Script

scripts/deploy.js
const hre = require('hardhat');

async function main() {
  const Greeter = await hre.ethers.getContractFactory('Greeter');
  const greeter = await Greeter.deploy('Hello Hardhat 2026!');
  await greeter.waitForDeployment();

  console.log('Greeter deployed to:', await greeter.getAddress());
}

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

Script deploys via ethers factory and logs the address. hre provides Hardhat Runtime Environment access. Copy to scripts/, run npx hardhat run scripts/deploy.js --network hardhat. For Sepolia: --network sepolia. Error handling with catch.

Deploy and Verify

Local: npx hardhat run scripts/deploy.js
Sepolia: npx hardhat run scripts/deploy.js --network sepolia
Verify: npx hardhat verify --network sepolia CONTRACT_ADDRESS "Hello Hardhat 2026!"

Console shows the address. Etherscan verification makes the contract public (source code).

Fork a Chain for Advanced Testing

hardhat.config.js (addition)
networks: {
  hardhat: {
    forking: {
      url: 'https://eth-mainnet.g.alchemy.com/v2/VOTRE_CLE_ALCHEMY',
      blockNumber: 20000000
    }
  }
},

Add this to hardhat.config.js to fork Mainnet (free Alchemy). Test on real state without gas costs. Example: Impersonate a whale for massive transfers. Restart npx hardhat node for REPL. Pitfall: Use a recent blockNumber to avoid stale forks.

Best Practices

  • Optimize Solidity: Enable optimizer (runs:200+) for <10% gas savings.
  • Test Coverage: Add npm i -D hardhat-gas-reporter solidity-coverage and integrate in config.
  • Secure Envs: Use dotenv for keys (.env + require('dotenv').config()).
  • Gas Reporting: Add gasReporter to hardhat.config.js for benchmarks.
  • Multi-Compiler: Support 0.8.x + legacy 0.5.x if needed.

Common Errors to Avoid

  • Exposed Private Key: Never hardcode in config; use process.env.PRIVATE_KEY.
  • Solidity Version Mismatch: pragma ^0.8.28 needs config 0.8.28 or compile fails.
  • No waitForDeployment(): Async tests fail without it (post-London fork).
  • Unfunded Network: Check Sepolia faucet; test local first.

Next Steps