Skip to content

Hardhat Tutorial — Ethereum Development Framework

DodaTech 8 min read

In this tutorial, you'll learn about Hardhat Tutorial. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

What You'll Learn

Set up a professional Ethereum development environment with Hardhat — compile, test, debug, and deploy Solidity smart contracts.

Why It Matters

Hardhat is the industry standard for Ethereum development. It provides local Blockchain, console debugging, and task automation.

Prerequisites

  • Node.js 16+
  • Basic Solidity knowledge

Step 1: Initialize Project

mkdir hardhat-tutorial
cd hardhat-tutorial
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npx hardhat init

Select "Create an empty hardhat.config.js".

The first two commands create a directory and initialize a Node.js project with package.json. Installing hardhat gives you the core framework — compilation, testing, and task running. The @nomicfoundation/hardhat-toolbox plugin bundles essential tools: ethers for contract interaction, chai for assertions, hardhat-network for the local Blockchain, and hardhat-verify for Etherscan verification. Running npx hardhat init launches an interactive wizard — choose "Create an empty hardhat.config.js" to start with a clean configuration file.

Common pitfall: If npx hardhat init hangs or fails, try installing hardhat-toolbox version that matches your Hardhat version. Check compatibility in the Hardhat documentation.

Step 2: Write a Contract

// contracts/Token.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract LearnToken is ERC20 {
    constructor(uint256 initialSupply)
        ERC20("LearnToken", "LRN") {
        _mint(msg.sender, initialSupply * 10 ** decimals());
    }
}
npm install @openzeppelin/contracts

This ERC-20 token contract uses OpenZeppelin's battle-tested implementation rather than writing one from scratch. Importing ERC20.sol gives you the standard token interface — transfer, balanceOf, approve, transferFrom — plus all the event emissions and error handling that make a token compatible with wallets and exchanges.

The constructor takes initialSupply as a raw number (e.g., 1000000) and multiplies it by 10 ** decimals() (defaults to 18 decimals) to account for the decimal places. msg.sender is the address deploying the contract, and _mint allocates the entire initial supply to them.

The npm install command adds @openzeppelin/contracts as a dependency. Without this import, the compiler cannot resolve the ERC20 base contract.

Step 3: Write Tests

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

describe("LearnToken", function () {
  it("Should mint initial supply to deployer", async function () {
    const [owner] = await ethers.getSigners();
    const Token = await ethers.getContractFactory("LearnToken");
    const token = await Token.deploy(1000000);

    const balance = await token.balanceOf(owner.address);
    expect(balance).to.equal(
      ethers.parseEther("1000000")
    );
  });

  it("Should allow transfers", async function () {
    const [owner, addr1] = await ethers.getSigners();
    const Token = await ethers.getContractFactory("LearnToken");
    const token = await Token.deploy(1000000);

    await token.transfer(addr1.address, ethers.parseEther("100"));
    expect(await token.balanceOf(addr1.address)).to.equal(
      ethers.parseEther("100")
    );
  });
});

Tests in Hardhat use Mocha (the describe/it structure) with Chai for assertions and ethers.js for Blockchain interaction. ethers.getSigners() returns accounts from the Hardhat Network — the first one is the default deployer. ethers.getContractFactory("LearnToken") loads the compiled ABI and bytecode. .deploy(1000000) deploys the contract on the local Hardhat Network and returns a contract instance.

The first test checks that the deployer's balance equals 1000000 tokens (converted to wei via ethers.parseEther). The second test transfers 100 tokens to addr1 and verifies the recipient's balance. Each it block is independent — Hardhat resets the network state between tests by default.

Why write tests? Smart contracts manage real money. A single bug can cost millions. Tests catch issues before deployment, document expected behavior, and let you refactor with confidence. Always write tests before deploying.

Step 4: Run Tests

npx hardhat test

# Output:
#   LearnToken
#     ✓ Should mint initial supply to deployer
#     ✓ Should allow transfers
#   2 passing (1s)

npx hardhat test compiles the contracts automatically (if they have changed), starts the local Hardhat Network, and runs every test file in the test/ directory. The output shows each test passing with a checkmark. The (1s) at the end indicates the entire test suite completed in under one second because Hardhat Network runs in-process with instant block times.

What happens if a test fails? Hardhat shows a detailed error trace including the Solidity source line that caused the failure, the call stack, and the state of local variables. This is much more informative than raw EVM reverts.

Step 5: Deploy Locally

// scripts/deploy.js
async function main() {
  const [deployer] = await ethers.getSigners();
  console.log("Deploying with:", deployer.address);

  const Token = await ethers.getContractFactory("LearnToken");
  const token = await Token.deploy(1000000);
  await token.waitForDeployment();

  console.log("Token deployed to:", await token.getAddress());
}

main().catch(console.error);
npx hardhat run scripts/deploy.js

The deploy script follows the same pattern as the test setup. It gets the deployer account, creates a contract factory, and deploys. waitForDeployment() waits for the Transaction to be mined (on Hardhat Network this is instantaneous). getAddress() retrieves the deployed contract's address.

Running locally (npx hardhat run scripts/deploy.js) deploys to the built-in Hardhat Network, which starts and stops with the command — no configuration needed. The contract address is printed to the console. You can interact with it using Hardhat Console afterwards.

Step 6: Deploy to Testnet

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

module.exports = {
  solidity: "0.8.19",
  networks: {
    sepolia: {
      url: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`,
      accounts: [process.env.PRIVATE_KEY],
    },
  },
  etherscan: {
    apiKey: process.env.ETHERSCAN_KEY,
  },
};
# Set environment variables first
export ALCHEMY_KEY=your_key
export PRIVATE_KEY=your_deployer_private_key

npx hardhat run scripts/deploy.js --network sepolia

Configuring a network in hardhat.config.js tells Hardhat where to send transactions. The url is an RPC endpoint — you get this from Alchemy, Infura, or a self-hosted node. The accounts array takes private keys (with the 0x prefix) for the wallets that will deploy and interact.

Sepolia is a recommended Ethereum testnet because it uses proof-of-stake like mainnet and has reliable RPC infrastructure. Never use your mainnet private key for testing. Generate a dedicated key, fund it with Sepolia ETH from a faucet (search "Sepolia faucet"), and use it only for testnet work.

The --network sepolia flag overrides the default local network. Without it, Hardhat always uses the built-in local network.

Security warning: Never commit PRIVATE_KEY to version control. Use environment variables injected at runtime, or use Hardhat's dotenv integration. A leaked private key means complete loss of control over that wallet.

Step 7: Verify on Etherscan

npx hardhat verify --network sepolia DEPLOYED_ADDRESS 1000000

Verification uploads your Solidity source code to Etherscan so anyone can read it and match it against the deployed bytecode. Verified contracts get a green checkmark on Etherscan, and users can read the ABI, source code, and interact with the contract directly from the Etherscan interface.

The command arguments are: --network sepolia (which testnet), the deployed contract address (replace DEPLOYED_ADDRESS with the actual address), and the constructor arguments (1000000) in the order they appear in the constructor. If your constructor takes multiple arguments, list them all here.

Prerequisite: Set ETHERSCAN_KEY in your environment. Get a free API key from etherscan.io. Without this key, the verification request is rejected.

Common error: Verification fails if the constructor argument encoding is wrong. For complex argument types (addresses, arrays), wrap them in quotes.

Console Debugging

npx hardhat console

# In the console:
const [owner] = await ethers.getSigners();
const Token = await ethers.getContractFactory("LearnToken");
const token = await Token.deploy(1000000);
(await token.balanceOf(owner.address)).toString();

Hardhat Console is an interactive JavaScript REPL connected to the local Hardhat Network. Use it for quick experimentation without writing a script — test function calls, inspect state, debug revert reasons, and iterate on contract interactions in real time.

The console has ethers and your contract factories pre-loaded. If you type a contract call that reverts, the console prints the full Solidity stack trace showing exactly which line caused the failure and why. This is the fastest way to debug contract logic during development.

Tip: Use .exit or Ctrl+C twice to leave the console.

Common Errors

1. Solidity version mismatch

Error: Version "0.8.19" is not compatible with OpenZeppelin Contracts

OpenZeppelin pragma ranges must overlap with your contract's pragma. Check the installed @openzeppelin/contracts version and adjust Solidity in hardhat.config.js to match. Run npm view @openzeppelin/contracts version to see what you have installed.

2. Network connection timeout when deploying to testnet

Error: could not detect network (event "noNetwork")

Your RPC URL may be wrong or the provider may be down. Double-check the url in your hardhat.config.js. Test the endpoint with curl $ALCHEMY_URL. If using a free tier, check rate limits.

3. Insufficient funds for testnet deployment

Error: insufficient funds for gas * price + value

Get free Sepolia ETH from a faucet. Search "Sepolia faucet" and send 0.1-0.5 ETH to your testnet wallet address. Deployment typically costs less than 0.01 ETH.

4. Contract not found during verification

Error: The address ... has no bytecode. Did you deploy to the correct network?

You are likely verifying on the wrong network or the address is incorrect. Confirm you passed --network sepolia and that DEPLOYED_ADDRESS matches the address printed during deployment.

5. Private key format error

Error: Invalid Private Key

Ensure the private key is 64 hex characters (32 bytes) without the 0x prefix in your environment variable — or include the 0x prefix consistently. Use process.env.PRIVATE_KEY.startsWith("0x") to check.

Practice

  1. Add a new test: Write a test that transfers tokens from addr1 to addr2 and verifies both balances changed. You will need to use token.connect(addr1).transfer(addr2.address, ...) to send from a non-owner account.

  2. Add a test for insufficient balance: Write a test that expects a revert when a user tries to transfer more tokens than they own. Use expect(token.connect(addr1).transfer(owner.address, 1)).to.be.revertedWith("ERC20: insufficient allowance") or similar.

  3. Verify on Sepolia: Deploy the contract to Sepolia and run the verify command. Check the contract page on Etherscan to see the verified source code with the green checkmark.

  4. Challenge: Extend the contract with a mint function that only the owner can call (use OpenZeppelin's Ownable). Write tests for the new function, deploy, and verify.

Summary

You set up a complete Hardhat development environment, wrote an ERC-20 token contract using OpenZeppelin, tested it with Mocha and Chai, deployed to a local network and to Sepolia testnet, and verified the contract on Etherscan. This workflow — init, write, test, deploy, verify — is the standard pipeline used by professional Solidity developers. Hardhat's local network, detailed stack traces, and plugin ecosystem make it the go-to framework for Ethereum smart contract development.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro