“Hardhat Tutorial 2026: Project Setup, Testing, Deployment & Verification — Complete Guide”
ATNO For Blockchain Developer5 min read·Just now--
The definitive end-to-end walkthrough for modern smart contract development. Learn TypeScript-first project architecture, Ignition deployments, Chai testing patterns, and automated verification — exactly how production Web3 teams use Hardhat today.
If you’re building smart contracts in 2026, you’ve probably heard the Foundry vs. Hardhat debates. Both are excellent. But Hardhat remains the industry standard for teams that prioritize TypeScript ecosystems, rich plugin architectures, and enterprise-grade CI/CD pipelines.
This guide walks you through a complete, production-ready Hardhat workflow. We’ll set up a TypeScript project, write and test a contract with modern Chai matchers, deploy to a testnet using Hardhat Ignition, and verify it on Etherscan — all in under an hour.
Let’s build.
📦 Step 1: Installation & Project Initialization
Prerequisites:
- Node.js
v20+(LTS recommended) npm,yarn, orpnpm- Basic familiarity with Solidity & TypeScript
mkdir my-hardhat-project && cd my-hardhat-project
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox dotenv
npx hardhat initWhen prompted, select:
- ✅ Create a TypeScript project
- ✅ Add
.gitignore - ✅ Install dependencies
Why @nomicfoundation/hardhat-toolbox?
In 2026, you no longer install individual plugins. The official toolbox bundles ethers v6, chai, mocha, solidity-coverage, hardhat-gas-reporter, and @nomicfoundation/hardhat-chai-matchers out of the box. It’s the modern baseline.
🗂️ Step 2: Project Structure & Configuration
Hardhat generates a clean, opinionated structure:
├── contracts/
│ └── SimpleVault.sol
├── ignition/
│ └── modules/
├── test/
│ └── SimpleVault.test.ts
├── hardhat.config.ts
├── .env
└── package.jsonhardhat.config.ts Breakdown
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import * as dotenv from "dotenv";dotenv.config();const config: HardhatUserConfig = {
solidity: {
version: "0.8.28",
settings: {
optimizer: { enabled: true, runs: 200 },
viaIR: true, // Enables IR compilation for better optimization
},
},
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL || "",
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
chainId: 11155111,
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY || "",
},
};export default config;
.env Setup
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
PRIVATE_KEY=0xyour_wallet_private_key
ETHERSCAN_API_KEY=your_etherscan_api_key⚠️ Never commit .env. Hardhat’s .gitignore handles it by default, but always double-check before pushing.
📝 Step 3: Writing Your First Contract
Create contracts/SimpleVault.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";contract SimpleVault is Ownable, ReentrancyGuard {
mapping(address => uint256) public balances; event Deposited(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount); constructor() Ownable(msg.sender) {} function deposit() external payable {
require(msg.value > 0, "Must send ETH");
balances[msg.sender] += msg.value;
emit Deposited(msg.sender, msg.value);
} function withdraw(uint256 amount) external nonReentrant {
require(amount > 0, "Amount must be > 0");
require(balances[msg.sender] >= amount, "Insufficient balance"); balances[msg.sender] -= amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
emit Withdrawn(msg.sender, amount);
} function getBalance() external view returns (uint256) {
return balances[msg.sender];
}
}
This is intentionally simple but production-minded: it uses OpenZeppelin guards, emits events, and follows checks-effects-interactions.
🧪 Step 4: Testing with Chai & Mocha
Hardhat’s testing suite is built on mocha + chai + ethers v6. Create test/SimpleVault.test.ts:
import { expect } from "chai";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { ethers } from "hardhat";async function deployVaultFixture() {
const [owner, addr1, addr2] = await ethers.getSigners();
const Vault = await ethers.getContractFactory("SimpleVault");
const vault = await Vault.deploy();
return { vault, owner, addr1, addr2 };
}describe("SimpleVault", function () {
it("should deploy and set owner correctly", async function () {
const { vault, owner } = await loadFixture(deployVaultFixture);
expect(await vault.owner()).to.equal(owner.address);
}); it("should allow deposits and update balance", async function () {
const { vault, addr1 } = await loadFixture(deployVaultFixture);
const depositAmount = ethers.parseEther("1.0"); await expect(vault.connect(addr1).deposit({ value: depositAmount }))
.to.emit(vault, "Deposited")
.withArgs(addr1.address, depositAmount); expect(await vault.getBalance()).to.equal(depositAmount);
}); it("should revert on withdrawal with insufficient balance", async function () {
const { vault, addr1 } = await loadFixture(deployVaultFixture);
await expect(vault.connect(addr1).withdraw(ethers.parseEther("0.5")))
.to.be.revertedWith("Insufficient balance");
}); it("should handle successful withdrawal", async function () {
const { vault, addr1 } = await loadFixture(deployVaultFixture);
const depositAmount = ethers.parseEther("2.0");
const withdrawAmount = ethers.parseEther("1.0"); await vault.connect(addr1).deposit({ value: depositAmount }); const initialBalance = await ethers.provider.getBalance(addr1.address);
const tx = await vault.connect(addr1).withdraw(withdrawAmount);
const receipt = await tx.wait();
const gasUsed = receipt!.gasUsed * receipt!.gasPrice!;
const finalBalance = await ethers.provider.getBalance(addr1.address); expect(finalBalance).to.be.closeTo(
initialBalance + withdrawAmount - gasUsed,
ethers.parseEther("0.001") // tolerance for gas variance
);
});
});
Run tests:
npx hardhat test💡 Pro Tip: Use loadFixture to snapshot network state. It cuts test execution time by 60–80% compared to redeploying every it() block.
🚀 Step 5: Deployment with Hardhat Ignition
Legacy scripts/deploy.ts is deprecated. Hardhat Ignition is the official, declarative deployment engine in 2026.
Create ignition/modules/DeploySimpleVault.ts:
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";const DeploySimpleVault = buildModule("DeploySimpleVault", (m) => {
const vault = m.contract("SimpleVault");
return { vault };
});export default DeploySimpleVault;
Deploy to Sepolia:
npx hardhat ignition deploy ./ignition/modules/DeploySimpleVault.ts --network sepoliaIgnition handles:
- Deterministic deployment addresses (if salted)
- Dependency graphs
- Automatic gas estimation & retries
- Dry-run mode (
-dry-run)
Output includes the deployed contract address and transaction hash. Save it.
🔍 Step 6: Etherscan Verification
Verification is mandatory for transparency and frontend integrations. Hardhat bundles @nomicfoundation/hardhat-verify.
Configure hardhat.config.ts (already done in Step 2). Then run:
npx hardhat verify --network sepolia <DEPLOYED_CONTRACT_ADDRESS>For contracts with constructor arguments:
npx hardhat verify --network sepolia <ADDRESS> "arg1" 12345✅ Multi-Chain Note: Etherscan’s API works for Ethereum mainnet & testnets. For Arbitrum, Base, BNB Chain, etc., use their respective explorers (Arbiscan, BscScan) by adding customChains to your etherscan config. Hardhat’s verifier auto-routes based on chainId.
🛠️ Step 7: 2026 Best Practices & Pro Tips
📌 Final Thoughts
Hardhat in 2026 isn’t just a compiler — it’s a full lifecycle engineering platform. With Ignition deployments, TypeScript-native type generation, parallel testing, and automated verification, it scales from personal side projects to multi-million-dollar protocol stacks.
The tools are free. The documentation is excellent. The only bottleneck is consistent practice. Deploy to a testnet today. Break it. Fix it. Verify it. Repeat.
Stuck on a specific step? Drop your error message, network, or contract snippet in the comments. I’ll reply with exact fixes, config tweaks, or debugging commands.
🔖 Tags: #HardhatTutorial #HardhatSetup #HardhatDeploy #HardhatTesting #SmartContractDevelopmentFramework #Solidity2026 #Web3Engineering #dAppDevelopment
Want a ready-to-clone GitHub template with this exact setup, CI pipelines, and pre-configured network RPCs? Reply “TEMPLATE” and I’ll publish the repo + setup automation script next.