
Hardhat
- 6 installs
- 4 repo stars
- Updated February 25, 2026
- hairyf/blockchain-master
Develop Ethereum smart contracts with Hardhat - compile, test, deploy with Ignition, configure networks, and extend via tasks and plugins.
About
Hardhat is an Ethereum development environment with a task runner, built-in network, Ignition deployment, and plugins. A developer uses it to compile, test, and deploy Solidity contracts.
- Config, HRE, compiler settings, and build profiles
- Hardhat Ignition deployment plus tasks and plugin system
Hardhat by the numbers
- 6 all-time installs (skills.sh)
- Ranked #334 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Jul 13, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hairyf/blockchain-master --skill hardhatAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6 |
|---|---|
| repo stars | ★ 4 |
| Last updated | February 25, 2026 |
| Repository | hairyf/blockchain-master ↗ |
What it does
Develop Ethereum smart contracts with Hardhat - compile, test, deploy with Ignition, configure networks, and extend via tasks and plugins.
Files
Skill based on Hardhat (NomicFoundation/hardhat), generated 2026-02-09. Official docs: https://hardhat.org/docs
Hardhat is an Ethereum development environment: task runner (compile, test, run, node), built-in Hardhat Network, Hardhat Ignition for declarative deployment, and plugins (toolbox ethers/viem, Chai matchers, verify, etc.).
Core References
| Topic | Description | Reference |
|---|---|---|
| Getting started | Init, tasks, compile/test/deploy flow | core-getting-started |
| Configuration | hardhat.config, networks, solidity, paths, mocha | core-config |
| HRE | Hardhat Runtime Environment, network, artifacts, config | core-hre |
| Compiler config | Solidity version, optimizer, viaIR, settings | core-compiler-config |
| Build profiles | default vs production, --build-profile, solidity.profiles | core-build-profiles |
| Config variables | configVariable, keystore, secrets, env | core-config-variables |
| Tasks and plugins | HRE, tasks, plugins, creating tasks | core-tasks-plugins |
Features
| Topic | Description | Reference |
|---|---|---|
| Hardhat Network | In-process vs node, JSON-RPC, forking, network helpers | features-network |
| Hardhat Ignition | Declarative deployment, buildModule, Future, deploy | features-ignition |
| Deployment overview | Ignition vs scripts, network and keystore setup | features-deployment-overview |
| Deployment scripts | viem/ethers deploy in scripts, hardhat run, --build-profile | features-deployment-scripts |
| Multichain | Chain types (l1, op), --chain-type, network.connect chainType | features-multichain |
| Testing | loadFixture, Chai matchers, network helpers | features-testing |
| Testing (Viem + node:test) | hre.network.connect, viem assertions, network helpers | features-testing-viem |
| Solidity tests | .t.sol, setUp, fuzz, forge-std, cheatcodes | features-testing-solidity |
| Code coverage | --coverage, LCOV, HTML report | features-testing-coverage |
| Gas statistics | --gas-stats, per-function and deployment gas | features-testing-gas-stats |
| Toolbox and verify | Ethers vs Viem toolbox, contract verification | features-toolbox-verify |
Advanced
| Topic | Description | Reference |
|---|---|---|
| Cheatcodes | vm.prank, time, and other Solidity test cheatcodes | advanced-cheatcodes |
External Links
Generation Info
- Source:
sources/hardhat(https://github.com/NomicFoundation/hardhat) - Git SHA:
614f405eadd3ea6cf71640ae46d19334f37fd971 - Generated: 2026-02-09
- Docs used: README.md, https://hardhat.org/docs (getting started, config, ignition, testing, toolbox, verify)
Solidity Test Cheatcodes
Hardhat supports cheatcodes in Solidity tests: special functions (typically on vm) that manipulate EVM state and execution context. Use them with Solidity tests (.t.sol or under test/) and, when needed, with forge-std for a Test base contract.
Environment (msg.sender / tx.origin)
- vm.prank(caller) – Next call (including static calls) sees
msg.sender = caller. Does not affect delegate calls. - vm.prank(caller, origin) – Set both
msg.senderandtx.originfor the next call. - vm.prank(caller, delegateCall) – When
delegateCallis true, setsmsg.senderfor the next delegate call.
Example: test a function that requires msg.sender == owner by pranking as another account:
vm.prank(nonOwner);
myContract.withdraw(); // reverts if only owner can callOther cheatcodes
The full set is documented in the Hardhat cheatcodes reference. Common categories include time manipulation, storage/state, and FFI. Enable ffi in test.solidity config if you use the FFI cheatcode.
Configuration
- Solidity test execution (including which cheatcodes are allowed) is configured under
test.solidityinhardhat.config(e.g.ffi: true,from: "0x..."). See Solidity tests configuration.
Key points
- Use
vm.prank(and related overloads) to changemsg.sender/tx.originfor the next call in Solidity tests. - Combine with forge-std's
Testand assertion helpers for readable tests. Check the cheatcodes reference for the full list.
<!-- Source references:
- https://hardhat.org/docs/reference/cheatcodes/cheatcodes-overview
- https://hardhat.org/docs/reference/cheatcodes/environment/prank
- https://hardhat.org/docs/guides/testing/using-solidity
-->
Build Profiles
Build Profiles let you use different Solidity compiler settings for different workflows (e.g. fast dev builds vs optimized deployment builds).
Built-in profiles
- default – Used by most tasks when you don’t pass
--build-profile. Tuned for development speed and experience. - production – Recommended for deployments. Optimizer and isolated builds are enabled by default. Hardhat Ignition uses this by default when deploying.
Config without explicit profiles
If you set solidity without a profiles key, you are configuring the default profile:
solidity: {
version: "0.8.29",
settings: { optimizer: { enabled: true, runs: 200 } },
}Defining custom profiles
Use solidity.profiles to define named profiles:
import { defineConfig } from "hardhat/config";
export default defineConfig({
solidity: {
profiles: {
myProfile: {
version: "0.8.29",
settings: { optimizer: { enabled: true, runs: 200 } },
},
},
npmFilesToBuild: [/* ... */],
},
});Each profile can use the full Solidity configuration schema.
Choosing a profile
Pass --build-profile <name> when running Hardhat:
npx hardhat test --build-profile production
npx hardhat run scripts/deploy.ts --build-profile production --network sepoliaUse the same profile for build and verify so bytecode matches (e.g. deploy and verify with production).
Key points
- Use default for day-to-day dev and tests; use production for deployment and verification.
- Always use the same build profile when deploying and when running
hardhat verify.
<!-- Source references:
- https://hardhat.org/docs/guides/writing-contracts/build-profiles
- https://hardhat.org/docs/reference/configuration#solidity-configuration
- https://hardhat.org/docs/guides/writing-contracts/isolated-builds
-->
Configuring the Compiler
Solidity compilation in Hardhat is configured in hardhat.config under the solidity key: version, optimizer, and other solc settings.
Version and settings
import { defineConfig } from "hardhat/config";
export default defineConfig({
solidity: {
version: "0.8.29",
settings: {
// solc options
},
},
});Optimizer
solidity: {
version: "0.8.29",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
}runs trades deployment cost vs runtime cost; higher values favor runtime.
Via-IR
IR-based codegen enables more optimizations at the cost of compile time:
solidity: {
version: "0.8.29",
settings: {
viaIR: true,
},
}Other settings
settings accepts any options supported by the chosen solc version. See the Solidity compiler docs.
Advanced
- Custom Solidity compiler, multiple versions, overrides: see Hardhat cookbook and Solidity configuration reference.
- Build profiles and isolated builds: different compiler configs per use case or deployment.
Key points
- Set
solidity.versionandsolidity.settingsin config; use optimizer andrunsfor production builds. - Verification and deployment should use the same build profile/compiler settings so bytecode matches.
<!-- Source references:
- https://hardhat.org/docs/guides/writing-contracts/configuring-the-compiler
- https://hardhat.org/docs/reference/configuration#solidity-configuration
-->
Configuration Variables
Hardhat uses Configuration Variables for values that should not be committed (RPC URLs with API keys, private keys). They are resolved at runtime from environment variables or from the hardhat-keystore plugin.
configVariable
Use configVariable(name) or configVariable(name, format) in hardhat.config:
import { configVariable, defineConfig } from "hardhat/config";
export default defineConfig({
networks: {
sepolia: {
type: "http",
url: configVariable("SEPOLIA_RPC_URL"),
accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
},
},
});Variables are lazy: resolved only when needed, so you can run tasks that don’t use a network without defining every variable.
Environment variables
When not using keystore, Hardhat reads the value from an env var with the same name:
SEPOLIA_RPC_URL='https://eth-sepolia.g.alchemy.com/v2/KEY' npx hardhat run ./script.ts --network sepoliaFormat string
Use the second argument to inject the variable into a template (e.g. store only the API key):
url: configVariable(
"ALCHEMY_API_KEY",
"https://eth-sepolia.g.alchemy.com/v2/{variable}",
),hardhat-keystore
Store secrets encrypted so they aren’t in env or on disk in plain text:
npx hardhat keystore set SEPOLIA_RPC_URL
npx hardhat keystore set SEPOLIA_PRIVATE_KEYFirst run prompts for a keystore password. Later, Hardhat prompts for that password when a variable is needed. Use the same configVariable("SEPOLIA_RPC_URL") in config; the plugin supplies the value from the keystore.
Tasks: keystore list, keystore get <key>, keystore delete <key>, keystore change-password, keystore path, keystore rename <old> <new>.
Development keystore
For local dev with non-sensitive values, use a separate keystore that doesn’t require a password:
npx hardhat keystore set --dev MY_LOCAL_RPC_URLKey points
- Prefer
configVariable("NAME")over hardcoding URLs and keys in config. - Use hardhat-keystore for private keys and API keys; use
--devfor local-only, non-sensitive values.
<!-- Source references:
- https://hardhat.org/docs/learn-more/configuration-variables
- https://hardhat.org/docs/explanations/configuration-variables
- https://hardhat.org/docs/plugins/hardhat-keystore
-->
Hardhat Configuration
Config is the closest hardhat.config.js (or .ts) from CWD. Export an object with defaultNetwork, networks, solidity, paths, mocha, and plugin usage.
Basic shape
module.exports = {
defaultNetwork: "sepolia",
networks: {
hardhat: { /* optional overrides for in-process network */ },
localhost: { url: "http://127.0.0.1:8545" },
sepolia: {
url: "https://sepolia.infura.io/v3/<key>",
accounts: [privateKey1, privateKey2],
},
},
solidity: {
version: "0.8.28",
settings: { optimizer: { enabled: true, runs: 200 } },
},
paths: { sources: "./contracts", tests: "./test", cache: "./cache", artifacts: "./artifacts" },
mocha: { timeout: 40000 },
};Networks
- hardhat: Built-in in-process network; optional config (chainId, forking, etc.).
- JSON-RPC networks:
url(required), optionalchainId,accounts(array of hex private keys or"remote"),gas,gasPrice,gasMultiplier,httpHeaders,timeout. - HD wallet: set
accounts: { mnemonic: "...", path: "m/44'/60'/0'/0", initialIndex: 0, count: 20, passphrase: "" }. - defaultNetwork: If omitted, default is
"hardhat".
Solidity
- Single compiler:
solidity: "0.8.28"orsolidity: { version: "0.8.28", settings: { ... } }.settingsfollows solc Input JSON. - Multiple compilers:
solidity: { compilers: [ {...}, {...} ], overrides: { "contracts/Foo.sol": { version: "0.7.6" } } }. - EVM version: e.g.
settings: { evmVersion: "shanghai" }. Hardhat defaults toparisfor 0.8.20+ (avoids PUSH0 on chains that don’t support it).
Paths
All relative to project root (directory of config file): sources, tests, cache, artifacts. Override only what you need.
Mocha
mocha accepts standard Mocha options (e.g. timeout) for hardhat test.
Key points
- Config runs before every task; safe to require plugins or other tooling here.
- TypeScript: use
defineConfigandimportinhardhat.config.ts; ensure ts-node or equivalent is available.
<!-- Source references:
- https://hardhat.org/hardhat-runner/docs/config
- https://hardhat.org/hardhat-network/docs/reference#config
-->
Hardhat Getting Started
Hardhat is an Ethereum development environment. The main component is Hardhat Runner: a task runner. Every CLI invocation runs a task (e.g. compile, test). Functionality is extended via plugins.
Init and install
Project-local install (recommended):
npm init -y
npm install --save-dev hardhat@hh2
npx hardhat initnpx hardhat init offers: JavaScript/TypeScript project, or empty config. Use npx hardhat to list tasks.
Key tasks
| Task | Purpose |
|---|---|
npx hardhat compile | Compile Solidity; writes artifacts (and TypeChain if TS) |
npx hardhat test | Run Mocha tests (uses Hardhat Network by default) |
npx hardhat run <script> | Run a script after compiling |
npx hardhat node | Start standalone Hardhat Network JSON-RPC server |
npx hardhat ignition deploy <module> | Deploy via Hardhat Ignition module |
npx hardhat clean | Clear cache and artifacts |
npx hardhat help [task] | List tasks or show task help |
Default network is hardhat (in-process). Use --network <name> to target another network (e.g. localhost, sepolia).
Quick flow
1. Compile: npx hardhat compile → artifacts/, cache/ 2. Test: put tests in test/, use hre.ethers (or viem) and loadFixture from network-helpers 3. Deploy: define an Ignition module in ignition/modules/, then npx hardhat ignition deploy ./ignition/modules/Lock.ts 4. Standalone node: npx hardhat node → connect wallet/dapp to http://127.0.0.1:8545; run Hardhat with --network localhost to use it
Key points
- Tasks and plugins: override or add tasks via config/plugins.
- Config file:
hardhat.config.js(or.ts) at project root; Hardhat loads the closest from CWD. - Node.js: use Node 22+ for Hardhat 3; Hardhat 2 uses
hardhat@hh2.
<!-- Source references:
- https://hardhat.org/hardhat-runner/docs/getting-started
- https://hardhat.org/docs
-->
Hardhat Runtime Environment (HRE)
The HRE is the object that exposes Hardhat and plugin functionality when running a task, test, or script. Plugins inject into the HRE so their APIs are available everywhere the HRE is used.
Using the HRE
- In tasks: the HRE is passed as an argument to the task action.
- In TypeScript tests or scripts: import it:
import hre from "hardhat";You can also construct it manually via "hardhat/hre" if needed.
Main HRE properties
- network – Connect to live networks or create blockchain simulations (
network.connect(), network config). See network management. - artifacts – Read compilation artifacts (contract ABIs, bytecode, etc.) for the project.
- config – Resolved config Hardhat uses (after merging defaults and user config).
- userConfig – Raw user config from
hardhat.config. - tasks – Task manager to run Hardhat tasks.
- solidity – Solidity build system.
- hooks – Hook manager for plugins to customize behavior.
- globalOptions – Global CLI options.
- interruptions – User interruptions manager for plugin I/O.
- versions – Versions of Hardhat and key dependencies.
Named imports
You can import specific pieces from "hardhat":
import {
config,
userConfig,
artifacts,
network,
globalOptions,
hooks,
interruptions,
solidity,
tasks,
versions,
} from "hardhat";Key points
- Use
import hre from "hardhat"in scripts and tests; usehre.network,hre.artifacts, etc., or the named imports. - Plugins extend the HRE (e.g.
hre.ethers,hre.viemfrom toolboxes).
<!-- Source references:
- https://hardhat.org/docs/explanations/hardhat-runtime-environment
- https://hardhat.org/docs/explanations/network-management
-->
Hardhat Tasks and Plugins
Hardhat is task-based: each CLI run executes one task. Tasks can call other tasks. Plugins add or override tasks and extend the Hardhat Runtime Environment (HRE).
Built-in tasks
Examples: compile, clean, test, run, node, console, flatten, help, verify (when verify plugin is used). List all: npx hardhat.
Hardhat Runtime Environment (HRE)
In tasks, scripts, and tests you get the global hre (or import from "hardhat"). It provides:
- hre.config – resolved config
- hre.network – current network (name, config)
- hre.ethers – Ethers.js bindings (when
@nomicfoundation/hardhat-ethersor toolbox is used) - hre.artifacts –
hre.artifacts.readArtifact(name), etc. - hre.run(taskName, args) – run another task programmatically
Plugins extend hre (e.g. toolbox adds ethers, network-helpers, chai matchers).
Plugins
Install and load in config:
require("@nomicfoundation/hardhat-toolbox");
// or
require("@nomicfoundation/hardhat-ignition-ethers");Plugin can: add tasks, extend HRE, override existing tasks (e.g. compile). Official plugins: toolbox (ethers or viem), ignition, chai-matchers, verify, network-helpers, etc.
Creating a task
In hardhat.config.js:
task("accounts", "List accounts").setAction(async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const a of accounts) console.log(await a.getAddress());
});Use addParam, addOptionalParam, addPositionalParam for arguments. Override existing task by redefining it (e.g. task("compile", ...)).
Key points
- Use
hre.run("compile")inside scripts/tasks to ensure artifacts are up to date. - Plugin API:
extendEnvironment,extendConfig,task,subtask– see “Building plugins” in docs.
<!-- Source references:
- https://hardhat.org/hardhat-runner/docs/advanced/hardhat-runtime-environment
- https://hardhat.org/hardhat-runner/docs/advanced/create-task
- https://hardhat.org/hardhat-runner/docs/advanced/building-plugins
-->
Deploying with Scripts
Scripts in scripts/ can deploy contracts using the Hardhat Runtime Environment. Use network.connect() to get a viem or ethers instance tied to the target network, then deploy and optionally run post-deploy calls.
Script with Viem
import { network } from "hardhat";
const { viem, networkName } = await network.connect();
const client = await viem.getPublicClient();
console.log(`Deploying Counter to ${networkName}...`);
const counter = await viem.deployContract("Counter");
console.log("Counter address:", counter.address);
console.log("Calling counter.incBy(5)");
const tx = await counter.write.incBy([5n]);
await client.waitForTransactionReceipt({ hash: tx, confirmations: 1 });
console.log("Deployment successful!");Script with Ethers
import { network } from "hardhat";
const { ethers, networkName } = await network.connect();
console.log(`Deploying Counter to ${networkName}...`);
const counter = await ethers.deployContract("Counter");
await counter.waitForDeployment();
console.log("Counter address:", await counter.getAddress());
const tx = await counter.incBy(5n);
await tx.wait();
console.log("Deployment successful!");Running the script
npx hardhat run scripts/deploy-counter.ts --network sepoliaFor production deployments, use the same build profile as verification so bytecode matches:
npx hardhat run scripts/deploy-counter.ts --build-profile production --network sepoliaPrerequisites
- Network config (e.g.
sepolia) withurlandaccounts(preferconfigVariableand keystore). - Follow the deployment overview setup first.
Key points
- Use
network.connect()to getviemorethersbound to the current network. - Use
viem.deployContract("Name")orethers.deployContract("Name"); then call methods and wait for receipts as needed. - Run with
--network <name>and--build-profile productionwhen deploying for verification.
<!-- Source references:
- https://hardhat.org/docs/guides/deployment/using-scripts
- https://hardhat.org/docs/guides/deployment
-->
Hardhat Ignition
Hardhat Ignition is a declarative deployment system: you define Ignition modules (contract instances and calls), and Ignition executes them (order, parallelism, resume, error recovery).
Install and config
Toolbox (ethers or viem) often includes Ignition. Otherwise:
npm add --save-dev @nomicfoundation/hardhat-ignition-viemIn config:
import hardhatIgnitionViemPlugin from "@nomicfoundation/hardhat-ignition-viem";
export default defineConfig({ plugins: [hardhatIgnitionViemPlugin], ... });Module definition
Modules live under ignition/modules/. Build with buildModule(id, callback):
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
export default buildModule("LockModule", (m) => {
const unlockTime = m.getParameter("unlockTime", 1893456000);
const lockedAmount = m.getParameter("lockedAmount", 1_000_000_000n);
const lock = m.contract("Lock", [unlockTime], { value: lockedAmount });
return { lock };
});- m.contract(name, constructorArgs, overrides) – deploy contract; returns a Future.
- m.call(contractFuture, methodName, args) – call after deployment.
- m.getParameter(key, default) – parameterizable values for reuse/resume.
- Return an object of Futures to expose for other modules or tooling.
Deploy
npx hardhat ignition deploy ignition/modules/Lock.ts --network localhostIgnition runs Futures in order (respecting dependencies), can run independent steps in parallel, and stores deployment state (e.g. ignition/deployments/chain-31337/) for resume and idempotency.
Key points
- Modules are declarative: no direct
deploy()in code; define what to deploy and call, Ignition executes. - Use parameters (
m.getParameter) for different environments or reruns without code change. - State directory enables resuming and adapting to module changes.
<!-- Source references:
- https://hardhat.org/ignition/docs
- https://hardhat.org/hardhat-runner/docs/getting-started#deploying-your-contracts
-->
Multichain Support
Hardhat 3 simulates chains by Chain Type. Tests and scripts can run against the correct chain behavior (e.g. OP Mainnet) instead of a generic EVM, so chain-specific bugs are caught early.
Chain types
- l1 – Ethereum Mainnet and its testnets (default for Solidity tests).
- op – OP Mainnet and its testnets (different RPC responses, gas/L1 gas, precompiles, etc.).
- generic – Permissive EVM approximation (similar to Hardhat 2).
Same Chain Type = same behavior. Specify the type when creating a simulation or running Solidity tests.
Solidity tests
Default is l1. Override with --chain-type:
npx hardhat test solidity --chain-type op
npx hardhat test --chain-type opScripts and Network Manager
When connecting via the Network Manager (e.g. in scripts), pass chainType so the simulation matches the target chain:
import { network } from "hardhat";
const { viem } = await network.connect({
network: "hardhatOp",
chainType: "op",
});
const publicClient = await viem.getPublicClient();
// Chain-specific APIs (e.g. estimateL1Gas for OP) are available when chainType is "op".You can also set the chain type for a network in config; see the Network Manager reference.
Why it matters
Chain Type changes RPC response shapes, how methods like eth_estimateGas work, gas/L1 gas handling, predeploys, precompiles, opcodes, and gas costs. Using the right type makes tests and scripts accurate for the chain you deploy to.
Key points
- Use
--chain-type op(or other) when running Solidity tests for OP or other supported chains. - Use
chainTypeinnetwork.connect({ chainType: "op" })in scripts so plugins (e.g. hardhat-viem) expose chain-specific APIs.
<!-- Source references:
- https://hardhat.org/docs/explanations/multichain-support
- https://hardhat.org/docs/reference/network-manager
-->
Hardhat Network
Hardhat Network is a local Ethereum node for development: deploy, test, and debug. It has first-class Solidity support (stack traces, console.log via hardhat/console.sol).
Modes
1. In-process (default): When you run a task with --network hardhat, Hardhat starts an in-memory instance; no separate process. 2. Standalone node: npx hardhat node starts a JSON-RPC server (default http://127.0.0.1:8545). Wallets and other tools connect to this URL. Run Hardhat with --network localhost to use the same node.
Config (networks.hardhat)
Common options (see full reference on hardhat-network docs):
- chainId
- forking:
url: "https://eth-mainnet.g.alchemy.com/v2/<key>", optionalblockNumber - accounts: same HD/array pattern as other networks; default gives funded accounts
JSON-RPC
Standard Ethereum JSON-RPC. Hardhat Network also supports extra methods (e.g. evm_snapshot, evm_revert, evm_increaseTime, evm_mine) used by testing helpers.
Network Helpers
@nomicfoundation/hardhat-network-helpers (included in toolbox) provides helpers such as:
- time.latest(), time.increaseTo(t), time.increase(n)
- loadFixture(fn) – run fixture once, snapshot, revert to snapshot per test
- mine(), mine(n)
Use in tests to control time and blocks without calling RPC directly.
Key points
- Use
loadFixturefor repeatable test state; avoids re-deploying every test. - For mainnet fork testing, set
networks.hardhat.forking.urland optionallyblockNumber.
<!-- Source references:
- https://hardhat.org/hardhat-network/docs
- https://hardhat.org/hardhat-network-helpers/docs
- https://hardhat.org/hardhat-runner/docs/getting-started#connecting-a-wallet-or-dapp-to-hardhat-network
-->
Code Coverage
Hardhat 3 has built-in code coverage for Solidity contracts. Use it to see which parts of your contracts are exercised by tests.
Usage
npx hardhat test --coverageCombined coverage of all tests is shown. Output:
- Terminal: Markdown summary
- coverage/: LCOV (
coverage/lcov.info) and HTML report (coverage/html/index.html)
Run coverage for a subset of tests:
npx hardhat test solidity contracts/Counter.t.sol --coverageHow it works
Hardhat instruments Solidity contracts with markers and measures coverage at runtime.
- Works with optimized code; results are stable across solc versions.
- Side effects:
allowUnlimitedContractSizeis forced true on simulated networks; gas costs and bytecode size increase in coverage mode.
HTML and LCOV
- Open
coverage/html/index.htmlin a browser for per-file, per-line coverage. - Use
coverage/lcov.infowith tools (e.g. CI) or the VS Code Coverage Gutters extension (“Coverage Gutters: Watch”) to see coverage in the editor.
Key points
- Use
--coveragewith thetesttask or subtasks (test solidity,test nodejs). - Coverage runs use different bytecode; do not use coverage builds for deployment or verification.
<!-- Source references:
- https://hardhat.org/docs/guides/testing/code-coverage
-->
Gas Statistics
Hardhat can report gas consumed by your contracts’ public functions during a test run. Use the --gas-stats flag to print a summary table.
Usage
npx hardhat test --gas-stats
npx hardhat test solidity --gas-stats
npx hardhat test nodejs --gas-statsOutput is per contract: for each public function called directly by tests you get min, average, median, max gas and call count; for deployment you get gas cost and bytecode size.
What is included
- Only public functions that are called directly by tests are included. A function called only indirectly (e.g. by another function) does not appear. Private/internal functions are never listed.
- Deployment: one row per deployed contract (gas and size).
Example
For a contract with inc(), incBy(uint256), reset(), and private _incInternal(): if tests call only inc() and incBy(5), the table shows only inc and incBy. reset and _incInternal do not appear.
Key points
- Use
--gas-statswithtestor its subtasks to inspect gas usage from the tests you ran. - Results depend on which tests run (e.g.
test solidityvstest nodejsyield different tables).
<!-- Source references:
- https://hardhat.org/docs/guides/testing/gas-statistics
-->
Solidity Tests
Hardhat supports Solidity tests out of the box: test contracts are deployed and their test functions are run by the test runner.
Test files and contracts
A file is a test file if:
- It lives under
contracts/and has the.t.solextension, or - It lives under
test/(default paths; configurable).
A contract in a test file is a test contract if it has at least one function whose name starts with test. The runner deploys each test contract and calls each of those functions; a revert means failure.
contract CounterTest {
function testInc() public {
Counter counter = new Counter();
counter.inc();
require(counter.count() == 1, "count should be 1");
}
}Fuzz tests
Functions that take parameters are fuzz tests: the runner calls them many times with random arguments.
function testIncBy(uint by) public {
Counter counter = new Counter();
counter.incBy(by);
require(counter.count() == by, "count should match the 'by' value");
}setUp
A setUp() function is run before each test. Use it to share deployment/setup:
contract CounterTest {
Counter counter;
function setUp() public {
counter = new Counter();
}
function testInc() public {
counter.inc();
require(counter.count() == 1, "count should be 1");
}
function testIncBy(uint by) public {
counter.incBy(by);
require(counter.count() == by, "count should match the 'by' value");
}
}Assertion libraries (forge-std)
For better failure messages and helpers, use forge-std:
npm add --save-dev 'github:foundry-rs/forge-std#v1.9.7'import { Test } from "forge-std/Test.sol";
contract CounterTest is Test {
function testIncBy(uint by) public {
Counter counter = new Counter();
counter.incBy(by);
assertEq(counter.count(), by, "count should match the 'by' value");
}
}Cheatcodes
Hardhat supports Solidity test cheatcodes (e.g. vm.prank, time, storage) to control EVM state. Use them with forge-std’s Test or the Hardhat cheatcodes API.
Example: change msg.sender for the next call with vm.prank(alice).
Running Solidity tests
npx hardhat test
npx hardhat test solidity
npx hardhat test solidity path/to/Test.t.solConfig
- Paths:
paths.tests.solidityto change the Solidity test directory. - Execution:
test.solidityin config (e.g.ffi: true,from: "0x...") — see Solidity tests configuration reference. - Multichain: use
--chain-type op(or other chain type) so tests run against a different chain simulation (e.g. OP Mainnet).
Key points
- Put tests in
contracts/*.t.solortest/*.sol; name test functionstest*; usesetUp()for shared setup. - Use forge-std and cheatcodes for clearer assertions and EVM manipulation.
<!-- Source references:
- https://hardhat.org/docs/guides/testing/using-solidity
- https://hardhat.org/docs/reference/configuration#solidity-tests-configuration
- https://hardhat.org/docs/reference/cheatcodes/cheatcodes-overview
-->
Testing with Viem and node:test
Hardhat can run TypeScript tests using viem and the Node.js test runner (node:test), with hardhat-viem, hardhat-viem-assertions, and hardhat-network-helpers for type-safe contract interaction and EVM helpers.
Setup
With a viem-based init (hardhat --init) the plugins are usually already present. Otherwise install:
npm add --save-dev @nomicfoundation/hardhat-viem @nomicfoundation/hardhat-viem-assertions @nomicfoundation/hardhat-node-test-runner @nomicfoundation/hardhat-network-helpers viemAdd them to the plugins array in hardhat.config.ts.
Network connection
Get a connected viem instance and network helpers by calling hre.network.connect() (or network.connect() from "hardhat"). This creates a fresh local chain simulation for tests:
import { describe, it } from "node:test";
import hre from "hardhat";
const { viem, networkHelpers } = await hre.network.connect();
describe("Counter", function () {
it("emits Increment when inc() is called", async function () {
const counter = await viem.deployContract("Counter");
await viem.assertions.emitWithArgs(
counter.write.inc(),
counter,
"Increment",
[1n],
);
});
});- viem.deployContract("ContractName") – deploy and get a typed contract instance.
- viem.assertions.emitWithArgs(tx, contract, "EventName", [args]) – assert the transaction emits the event with the given args.
- viem.assertions.revertWith(tx, "message") – assert the transaction reverts with the given reason.
Reverts and impersonation
To test as a different account (e.g. non-owner), use network helpers then pass account to the write call:
await networkHelpers.impersonateAccount(nonOwnerAddress);
await networkHelpers.setBalance(nonOwnerAddress, 10n ** 18n);
await viem.assertions.revertWith(
counter.write.inc({ account: nonOwnerAddress }),
"only the owner can increment the counter",
);Fixtures (loadFixture)
Use networkHelpers.loadFixture(fn) to run a setup function once and revert the chain to that state before each test. Avoids re-deploying in every test and keeps tests isolated:
async function deployCounterFixture() {
const counter = await viem.deployContract("Counter");
return { counter };
}
it("test one", async function () {
const { counter } = await networkHelpers.loadFixture(deployCounterFixture);
// ...
});Running tests
npx hardhat test
npx hardhat test nodejs
npx hardhat test test/Counter.tsMultichain
Pass chainType when connecting to simulate a different chain (e.g. OP Mainnet):
const { viem } = await hre.network.connect({ chainType: "op" });Type safety and build
Contract types come from the compiled artifacts. If types are wrong or stale, run npx hardhat build. In VS Code, run “TypeScript: Reload Project” if needed.
Key points
- Use
hre.network.connect()to getviemandnetworkHelpersfor the test run. - Use
viem.assertionsfor emit/revert; usenetworkHelpersfor impersonation, balance, andloadFixture. - Prefer
loadFixtureover deploying in every test.
<!-- Source references:
- https://hardhat.org/docs/guides/testing/using-viem
- https://hardhat.org/docs/plugins/hardhat-viem
- https://hardhat.org/docs/plugins/hardhat-viem-assertions
- https://hardhat.org/docs/plugins/hardhat-network-helpers
-->
Hardhat Testing
Tests run with npx hardhat test. Default stack: Mocha, Chai, Ethers (or Viem) via toolbox, Hardhat Network. Use fixtures and Chai matchers for stable, readable tests.
Fixtures (loadFixture)
Run a setup function once, snapshot Hardhat Network, and revert to that snapshot for each test:
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
import { expect } from "chai";
import hre from "hardhat";
async function deployFixture() {
const [owner, other] = await hre.ethers.getSigners();
const Lock = await hre.ethers.getContractFactory("Lock");
const lock = await Lock.deploy(unlockTime, { value: 1n * 10n ** 9n });
return { lock, owner, other };
}
describe("Lock", function () {
it("should set owner", async function () {
const { lock, owner } = await loadFixture(deployFixture);
expect(await lock.owner()).to.equal(owner.address);
});
});Chai matchers (@nomicfoundation/hardhat-chai-matchers)
- revert:
await expect(tx).to.be.reverted,await expect(tx).to.be.revertedWith("message") - events:
await expect(tx).to.emit(contract, "EventName").withArgs(arg1, arg2); useanyValuefor any arg - balance changes:
await expect(tx).to.changeEtherBalances([addr1, addr2], [delta1, delta2])
With toolbox, matchers are registered automatically when you import from hardhat-chai-matchers or use the toolbox network-helpers.
Network helpers in tests
From @nomicfoundation/hardhat-toolbox/network-helpers (or hardhat-network-helpers):
- time.latest(), time.increaseTo(t), time.increase(n) – time manipulation
- mine(n) – mine blocks
Example: advance time then withdraw:
await time.increaseTo(unlockTime);
await expect(lock.withdraw()).not.to.be.reverted;Key points
- Prefer
loadFixtureover deploying in everyit()for speed and isolation. - Use matchers for reverts and events instead of manual try/catch or event parsing.
- Run tests on default
hardhatnetwork unless you need a forked or external network.
<!-- Source references:
- https://hardhat.org/hardhat-runner/docs/guides/test-contracts
- https://hardhat.org/hardhat-chai-matchers/docs
- https://hardhat.org/hardhat-runner/docs/getting-started#testing-your-contracts
-->
Hardhat Toolbox and Verification
Toolbox
Two main stacks:
- @nomicfoundation/hardhat-toolbox – Ethers.js v6, Mocha, Chai matchers, network-helpers, Ignition (ethers), TypeChain, Solidity stack traces.
- @nomicfoundation/hardhat-toolbox-viem – Same but with Viem instead of Ethers; use
hre.viemand Viem-based Ignition.
Install one per project. In config: require("@nomicfoundation/hardhat-toolbox") or use the viem toolbox. Use hre.ethers.getSigners(), getContractFactory, etc., or the Viem equivalents when using viem toolbox.
Contract verification (@nomicfoundation/hardhat-verify)
Verify contracts on Etherscan (or compatible explorers):
1. Add plugin and set network URLs + explorer API key in config. 2. After deploy, run:
npx hardhat verify --network <network> <contractAddress> <constructorArg1> <constructorArg2> ...Or use the programmatic API in a script/task. Config example:
networks: {
sepolia: { url: "...", accounts: [...] },
},
etherscan: {
apiKey: { sepolia: "<ETHERSCAN_API_KEY>" },
},For custom chains, set etherscan.customChains with network, chainId, and urls.apiURL / urls.browserURL.
Key points
- Toolbox pulls in Ethers (or Viem), Ignition, Chai matchers, network-helpers; no need to wire them manually.
- Verification requires correct constructor args and network; use same compiler settings as deploy.
<!-- Source references:
- https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-toolbox
- https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-toolbox-viem
- https://hardhat.org/hardhat-runner/docs/guides/verifying
- https://hardhat.org/hardhat-verify/docs
-->