Hardhat Forking Diagnostics: Spotting and Fixing State Mismatch Issues
When a Hardhat fork doesn’t reflect mainnet data, transactions revert or gas estimates skew, the problem often lies in the fork configuration. This guide walks through symptoms, causes, and step‑by‑step checks to restore a consistent forked state.
19 Jan 2026, 12:38 UTC

Problem Statement
When you run npx hardhat node with forking enabled, you expect the local node to mirror a snapshot of the target mainnet. Common symptoms of a mis‑configured fork include:
- Contract calls return default values or revert immediately.
- Gas estimates are wildly off or transactions fail with “revert” even though they succeed on mainnet.
- State changes you perform in tests are not persisted between runs.
- Hardhat prints errors about missing or unsupported
forkingoptions.
Root Causes – Quick Reference
| Cause | Typical Symptom |
|---|---|
| Invalid or unreachable RPC URL | Empty node state, no contracts deployed |
| Incorrect fork block number | Stale or too recent state, missing upgrades |
Missing or wrong chainId | Signature mismatches, address format errors |
| Plugin conflicts overriding fork settings | Fork settings ignored, default localhost state |
| Resource constraints (RAM/CPU) | Crashes, incomplete sync, intermittent failures |
| Version incompatibility between Hardhat, ethers.js, or plugins | Bootstrap errors, missing forking property |
Diagnostic Flow
- Verify the RPC URL
- Open
hardhat.config.jsand locate theforking.urlentry. - Run a quick JSON‑RPC call to confirm connectivity:
curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' https://YOUR_RPC_URL - Check that the response contains a block number and no error field.
- Open
- Confirm the fork block number
- In the config, ensure
forking.blockNumberis either omitted (default to latest) or set to a valid block that reflects the desired state. - Run a node with explicit parameters to see the logged fork block:
npx hardhat node --fork https://YOUR_RPC_URL --fork-block-number 17000000 - Observe console output: “Forked from block 17000000”.
- In the config, ensure
- Check chain ID alignment
- Make sure
chainIdin the config matches the RPC network (e.g., 1 for Ethereum mainnet). - When using
npx hardhat testwith a fork, Hardhat automatically sets the network’schainIdfrom the RPC, but explicit overrides can create mismatches.
- Make sure
- Inspect plugin order
- Hardhat loads plugins in the order they appear in
require()statements. If a plugin that manipulates network settings (e.g.,@nomiclabs/hardhat-waffle) comes before the fork configuration, it may override it. - Reorder
require()statements so that fork‑related plugins are loaded after the fork config block.
- Hardhat loads plugins in the order they appear in
- Monitor resource usage
- Forking a recent block can consume 8–12 GB RAM. Use
toporhtopto watch memory and CPU. - If the node stalls or exits, consider lowering the fork block number or increasing system resources.
- Forking a recent block can consume 8–12 GB RAM. Use
- Validate Hardhat and plugin versions
- Run
npm list hardhatandnpm list @nomiclabs/hardhat-ethersto confirm compatibility. - Upgrade to the latest stable releases if you see errors like “Missing forking property”.
- Run
Fixes Tied to Findings
- Invalid RPC URL – Replace with a working endpoint. If using Infura or Alchemy, ensure the project ID is correct and the endpoint is HTTPS.
- Wrong fork block number – Adjust
forking.blockNumberto a block after the contract upgrade you wish to test. Useeth_blockNumberto find the latest block. - Chain ID mismatch – Add
chainId: 1under the network config or remove any overridingchainIdsetting. - Plugin conflict – Reorder
require()calls or isolate fork settings in a dedicated network block. Example:require("@nomiclabs/hardhat-ethers"); require("@nomiclabs/hardhat-waffle"); module.exports = { networks: { hardhat: { forking: { url: process.env.ALCHEMY_MAINNET, blockNumber: 17000000 }, chainId: 1 } } }; - Resource constraints – Either increase RAM/CPU or fork an earlier block (e.g., 16000000) to reduce state size.
- Version incompatibility – Run
npm update hardhat @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffleand verify thathardhat.config.jsuses the updated API.
Escalation Criteria
If after applying the above fixes you still see:
- Hardhat logs an error about missing
forkingproperty or unsupported options. - Transactions revert with stack traces pointing to missing contract code.
- Gas estimates are consistently higher than on mainnet for the same operation.
At this point:
- Check the Hardhat issue tracker for known bugs in the current version.
- Open a minimal reproducible example and file a bug report with the Hardhat maintainers.
- Consider temporarily disabling forking and using a local testnet with a known state snapshot to isolate whether the issue is in your code or the fork itself.
Practical Verification Steps
- After configuring the fork, run a simple contract query:
npx hardhat console --network hardhat > const usdc = await ethers.getContractAt("IERC20", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); > const bal = await usdc.balanceOf("0x0000000000000000000000000000000000000000"); > console.log(bal.toString()); - Confirm the balance matches the mainnet value at the chosen block.
- Run a transaction that should succeed on mainnet and inspect the receipt:
const tx = await usdc.transfer("0xReceiver", 1000); const receipt = await tx.wait(); console.log(receipt.gasUsed.toString()); - Compare
gasUsedwith the value reported byeth_gasPriceon mainnet for the same block.
Limitations & Caveats
- Forked state is an in‑memory snapshot; it does not persist across node restarts unless you explicitly take a snapshot.
- Transactions that rely on pending blocks (e.g.,
block.timestampafter a recent transaction) may not reflect the final state. - Using a very recent block can expose you to reorgs; choose a stable block number for deterministic tests.
- Always keep your RPC provider key secret; do not commit it to version control.
Summary
Hardhat forking is a powerful tool, but its reliability hinges on correct configuration. By systematically checking the RPC URL, block number, chain ID, plugin order, system resources, and version compatibility, you can quickly isolate and resolve most state mismatch issues. Once the fork is stable, you can confidently run tests that depend on live contract data without risking real funds or network disruptions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.