Getting Started with Hardhat: Complete Setup Guide and Voting Contract Tutorial

VairamuthuVairamuthu
5 min read

What is Hardhat?

Hardhat is a comprehensive Ethereum development environment that simplifies the process of building, testing, and deploying smart contracts. It provides developers with a complete toolkit including a built-in Solidity compiler, testing framework, debugging tools, and a local blockchain network for development. The platform is designed around tasks and plugins, making it highly extensible and customizable for different project needs.

Setting Up Hardhat: Step-by-Step Installation

Prerequisites

Before installing Hardhat, ensure you have the following requirements:

  • Node.js v18 or later installed on your system

  • npm (Node Package Manager) or yarn for dependency management

  • A code editor like Visual Studio Code (recommended)

  • Basic familiarity with JavaScript and command line operations

Installation Process

Step 1: Create Project Directory

mkdir voting-hardhat-project
cd voting-hardhat-project

Step 2: Initialize npm Project

npm init -y

Step 3: Install Hardhat

npm install --save-dev hardhat

Step 4: Initialize Hardhat Project

npx hardhat init

When prompted, select "Create a JavaScript project" and follow the setup wizard. This will create the basic project structure and install necessary dependencies.

Step 5: Install Additional Dependencies

npm install --save-dev @nomicfoundation/hardhat-toolbox

Project Structure Overview

After initialization, your Hardhat project will have the following structure:

textvoting-hardhat-project/
├── contracts/          # Smart contract files (.sol)
├── scripts/           # Deployment scripts
├── test/             # Test files
├── hardhat.config.js # Hardhat configuration
├── package.json      # Project dependencies
└── README.md         # Project documentation

Creating a Simple Voting Contract

Now let's create a practical voting smart contract that demonstrates core blockchain concepts.

Contract Code

Create a new file contracts/SimpleVoting.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleVoting {
    struct Candidate {
        uint id;
        string name;
        uint voteCount;
    }

    mapping(uint => Candidate) public candidates;
    mapping(address => bool) public hasVoted;
    uint public candidatesCount;
    address public owner;

    event VoteCast(address indexed voter, uint indexed candidateId);

    constructor() {
        owner = msg.sender;
        addCandidate("Alice");
        addCandidate("Bob");
        addCandidate("Charlie");
    }

    function addCandidate(string memory _name) private {
        candidatesCount++;
        candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
    }

    function vote(uint _candidateId) public {
        require(!hasVoted[msg.sender], "You have already voted");
        require(_candidateId > 0 && _candidateId <= candidatesCount, "Invalid candidate ID");

        hasVoted[msg.sender] = true;
        candidates[_candidateId].voteCount++;

        emit VoteCast(msg.sender, _candidateId);
    }

    function getCandidate(uint _candidateId) public view returns (uint, string memory, uint) {
        require(_candidateId > 0 && _candidateId <= candidatesCount, "Invalid candidate ID");
        Candidate memory candidate = candidates[_candidateId];
        return (candidate.id, candidate.name, candidate.voteCount);
    }

    function getWinner() public view returns (string memory, uint) {
        uint highestVotes = 0;
        uint winnerId = 0;

        for (uint i = 1; i <= candidatesCount; i++) {
            if (candidates[i].voteCount > highestVotes) {
                highestVotes = candidates[i].voteCount;
                winnerId = i;
            }
        }

        return (candidates[winnerId].name, highestVotes);
    }
}

This voting contract includes:

  • Candidate Management: Stores candidate information with vote counts

  • Voting Prevention: Ensures each address can only vote once

  • Vote Tracking: Maintains accurate vote tallies

  • Winner Determination: Identifies the candidate with the most votes

  • Event Logging: Emits events for transparency

Writing Tests

Create a comprehensive test file test/SimpleVoting.js:

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("SimpleVoting", function () {
    let SimpleVoting;
    let simpleVoting;
    let owner;
    let addr1;
    let addr2;

    beforeEach(async function () {
        SimpleVoting = await ethers.getContractFactory("SimpleVoting");
        [owner, addr1, addr2] = await ethers.getSigners();

        simpleVoting = await SimpleVoting.deploy();
        await simpleVoting.waitForDeployment();
    });

    describe("Deployment", function () {
        it("Should set the right owner", async function () {
            expect(await simpleVoting.owner()).to.equal(owner.address);
        });

        it("Should create 3 initial candidates", async function () {
            expect(await simpleVoting.candidatesCount()).to.equal(3);

            const alice = await simpleVoting.getCandidate(1);
            const bob = await simpleVoting.getCandidate(2);
            const charlie = await simpleVoting.getCandidate(3);

            expect(alice[1]).to.equal("Alice");
            expect(bob[1]).to.equal("Bob");
            expect(charlie[1]).to.equal("Charlie");
        });
    });

    describe("Voting", function () {
        it("Should allow voting for valid candidates", async function () {
            await simpleVoting.connect(addr1).vote(1);

            const alice = await simpleVoting.getCandidate(1);
            expect(alice[2]).to.equal(1);
            expect(await simpleVoting.hasVoted(addr1.address)).to.be.true;
        });

        it("Should prevent double voting", async function () {
            await simpleVoting.connect(addr1).vote(1);

            await expect(
                simpleVoting.connect(addr1).vote(2)
            ).to.be.revertedWith("You have already voted");
        });

        it("Should reject invalid candidate IDs", async function () {
            await expect(
                simpleVoting.connect(addr1).vote(0)
            ).to.be.revertedWith("Invalid candidate ID");
        });
    });

    describe("Winner determination", function () {
        it("Should correctly determine winner", async function () {
            await simpleVoting.connect(addr1).vote(1); // Alice
            await simpleVoting.connect(addr2).vote(1); // Alice
            await simpleVoting.connect(owner).vote(2); // Bob

            const [winnerName, winnerVotes] = await simpleVoting.getWinner();
            expect(winnerName).to.equal("Alice");
            expect(winnerVotes).to.equal(2);
        });
    });
});

Creating Deployment Script

Create the deployment script scripts/deploy.js:

const { ethers } = require("hardhat");

async function main() {
    console.log("Deploying SimpleVoting contract...");

    const SimpleVoting = await ethers.getContractFactory("SimpleVoting");
    const simpleVoting = await SimpleVoting.deploy();

    await simpleVoting.waitForDeployment();

    console.log("SimpleVoting contract deployed to:", await simpleVoting.getAddress());

    // Display initial candidates
    console.log("\nInitial candidates:");
    for (let i = 1; i <= 3; i++) {
        const candidate = await simpleVoting.getCandidate(i);
        console.log(`ID: ${candidate[0]}, Name: ${candidate[1]}, Votes: ${candidate[2]}`);
    }
}

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

Compilation and Testing

Step 1: Compile the Contract

npx hardhat compile

Step 2: Run Tests

npx hardhat test

Step 3: Deploy to Local Network

hardhat run scripts/deploy.js

Deploying to Testnet

To deploy to a live testnet like Sepolia, you need to configure your hardhat.config.js file.

Configuration Setup

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

module.exports = {
    solidity: "0.8.0",
    networks: {
        sepolia: {
            url: process.env.SEPOLIA_RPC_URL,
            accounts: [process.env.PRIVATE_KEY],
            chainId: 11155111,
        },
    },
};

Environment Variables

Create a .env file in your project root:

SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_INFURA_KEY
PRIVATE_KEY=your_wallet_private_key

Deploy to Sepolia Testnet

Before deployment, ensure you have:

  1. Sepolia ETH for gas fees (get from faucets like sepoliafaucet.com)

  2. RPC URL from providers like Infura or Alchemy

  3. MetaMask wallet configured for Sepolia testnet

Deploy with:

hardhat run scripts/deploy.js --network sepolia

Interacting with Your Contract

Once deployed, you can interact with your voting contract using Hardhat console:

hardhat console --network sepolia

Example interactions:

const SimpleVoting = await ethers.getContractFactory("SimpleVoting");
const contract = SimpleVoting.attach("YOUR_CONTRACT_ADDRESS");

// Vote for a candidate
await contract.vote(1);

// Check winner
const [winner, votes] = await contract.getWinner();
console.log(`Winner: ${winner} with ${votes} votes`);

Best Practices and Security Considerations

  1. Testing First: Always write comprehensive tests before deployment

  2. Testnet Deployment: Test on testnets before mainnet deployment

  3. Security Audits: Consider professional audits for production contracts

  4. Gas Optimization: Optimize contract code for gas efficiency

  5. Access Control: Implement proper permission systems

  6. Event Logging: Use events for transparency and tracking

Common Troubleshooting

Network Issues: Ensure your network configuration matches the target blockchain

Gas Estimation: Check sufficient ETH balance for transaction fees

Version Compatibility: Use compatible Solidity and Hardhat versions

RPC Connection: Verify your RPC URL is correct and accessible

This comprehensive guide provides everything you need to get started with Hardhat and deploy your first voting smart contract. The combination of local development, thorough testing, and testnet deployment ensures a robust development workflow for blockchain applications.

0
Subscribe to my newsletter

Read articles from Vairamuthu directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vairamuthu
Vairamuthu