Hardhat Framework Skill
Expert-level usage of Hardhat, the most popular Ethereum development environment.
Capabilities
- Configuration: Set up hardhat.config.js for multi-network
- Testing: Write tests with ethers.js/viem
- Plugins: Use plugins (upgrades, gas-reporter, coverage)
- Local Network: Run Hardhat Network for development
- Deployment: Execute scripts and manage deployments
- Forking: Fork mainnet for testing
- TypeChain: Generate TypeScript typings
Installation
# Create project
mkdir my-project && cd my-project
npm init -y
# Install Hardhat
npm install --save-dev hardhat
# Initialize project
npx hardhat init
# Install common dependencies
npm install --save-dev @nomicfoundation/hardhat-toolbox
Configuration
hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("@openzeppelin/hardhat-upgrades");
require("hardhat-gas-reporter");
require("solidity-coverage");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
viaIR: false,
},
},
networks: {
hardhat: {
forking: {
url: process.env.MAINNET_RPC_URL,
blockNumber: 18000000,
},
},
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
},
mainnet: {
url: process.env.MAINNET_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
gasReporter: {
enabled: true,
currency: "USD",
coinmarketcap: process.env.COINMARKETCAP_API_KEY,
},
paths: {
sources: "./contracts",
tests: "./test",
cache: "./cache",
artifacts: "./artifacts",
},
};
TypeScript Configuration
// hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "@openzeppelin/hardhat-upgrades";
const config: HardhatUserConfig = {
solidity: "0.8.20",
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL || "",
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
},
},
};
export default config;
Testing
Basic Test
// test/Token.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Token", function () {
let token;
let owner;
let addr1;
beforeEach(async function () {
[owner, addr1] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
token = await Token.deploy();
await token.waitForDeployment();
});
describe("Deployment", function () {
it("Should set the right owner", async function () {
expect(await token.owner()).to.equal(owner.address);
});
it("Should assign total supply to owner", async function () {
const ownerBalance = await token.balanceOf(owner.address);
expect(await token.totalSupply()).to.equal(ownerBalance);
});
});
describe("Transactions", function () {
it("Should transfer tokens", async function () {
await token.transfer(addr1.address, 50);
expect(await token.balanceOf(addr1.address)).to.equal(50);
});
it("Should emit Transfer event", async function () {
await expect(token.transfer(addr1.address, 50))
.to.emit(token, "Transfer")
.withArgs(owner.address, addr1.address, 50);
});
it("Should fail if sender lacks funds", async function () {
await expect(
token.connect(addr1).transfer(owner.address, 1)
).to.be.revertedWith("Insufficient balance");
});
});
});
TypeScript Test
// test/Token.test.ts
import { expect } from "chai";
import { ethers } from "hardhat";
import { Token } from "../typechain-types";
describe("Token", function () {
let token: Token;
beforeEach(async function () {
const Token = await ethers.getContractFactory("Token");
token = await Token.deploy();
});
it("Should deploy successfully", async function () {
expect(await token.getAddress()).to.be.properAddress;
});
});
Fork Testing
const { expect } = require("chai");
const { ethers, network } = require("hardhat");
describe("Fork Test", function () {
const USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const WHALE = "0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503";
beforeEach(async function () {
// Impersonate whale account
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [WHALE],
});
});
it("Should transfer USDC from whale", async function () {
const whale = await ethers.getSigner(WHALE);
const usdc = await ethers.getContractAt("IERC20", USDC_ADDRESS);
const [, recipient] = await ethers.getSigners();
const amount = ethers.parseUnits("1000", 6);
await usdc.connect(whale).transfer(recipient.address, amount);
expect(await usdc.balanceOf(recipient.address)).to.equal(amount);
});
});
Deployment Scripts
Basic Deployment
// scripts/deploy.js
const hre = require("hardhat");
async function main() {
const Token = await hre.ethers.getContractFactory("Token");
const token = await Token.deploy();
await token.waitForDeployment();
console.log("Token deployed to:", await token.getAddress());
// Verify on Etherscan
if (hre.network.name !== "hardhat") {
await hre.run("verify:verify", {
address: await token.getAddress(),
constructorArguments: [],
});
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Upgradeable Deployment
// scripts/deploy-upgradeable.js
const { ethers, upgrades } = require("hardhat");
async function main() {
const Token = await ethers.getContractFactory("TokenV1");
// Deploy proxy
const token = await upgrades.deployProxy(Token, [], {
initializer: "initialize",
});
await token.waitForDeployment();
console.log("Proxy deployed to:", await token.getAddress());
}
main();
Upgrade Script
// scripts/upgrade.js
const { ethers, upgrades } = require("hardhat");
async function main() {
const PROXY_ADDRESS = "0x...";
const TokenV2 = await ethers.getContractFactory("TokenV2");
const token = await upgrades.upgradeProxy(PROXY_ADDRESS, TokenV2);
console.log("Token upgraded");
}
main();
Hardhat Tasks
// hardhat.config.js
task("accounts", "Prints accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
}
});
task("balance", "Prints balance")
.addParam("account", "The account address")
.setAction(async (taskArgs, hre) => {
const balance = await hre.ethers.provider.getBalance(taskArgs.account);
console.log(hre.ethers.formatEther(balance), "ETH");
});
Commands
# Compile
npx hardhat compile
# Test
npx hardhat test
npx hardhat test --grep "transfer"
# Coverage
npx hardhat coverage
# Run script
npx hardhat run scripts/deploy.js --network sepolia
# Console
npx hardhat console --network localhost
# Node
npx hardhat node
# Verify
npx hardhat verify --network mainnet <address>
Process Integration
| Process | Purpose |
|---------|---------|
| smart-contract-development-lifecycle.js | Full development |
| dapp-frontend-development.js | dApp integration |
| All token processes | Token deployment |
| All DeFi processes | Protocol deployment |
Best Practices
- Use TypeScript for type safety
- Configure multiple networks
- Use environment variables
- Enable gas reporting
- Generate coverage reports
- Verify contracts on explorers
See Also
skills/foundry-framework/SKILL.md- Alternative frameworkskills/openzeppelin/SKILL.md- Contract libraries- Hardhat Documentation