This is a large release in preparation of the first stable version of Hardhat 3!
Please read the list of changes below, the user migration guide, and if you are a plugin author, the plugins migration guide.
Highlights
New NetworkManager methods
We deprecated the network.connect() method as it was a common source of confusion. It still works, but will eventually be removed.
Instead, you can use the new methods network.create() and network.getOrCreate().
Unified compilation of contracts and Solidity tests
Previously, Hardhat 3 compiled your contracts and your Solidity tests independently. Starting with this version, they are built together, leading to faster compilation times and smaller compilation artifacts.
You can opt into the older behavior with the new splitTestsCompilation field in your solidity config. This is mainly useful if you rely on plugins that don't yet support unified compilation yet, or if your setup depends on test artifacts being isolated from contract artifacts.
Simulated networks now return the JSON-RPC code 3 on execution errors
If you run an in-process simulated network, or hardhat node, revert errors in JSON-RPC responses now use code 3 instead of the code used previously (-32603 or no code at all). This change aligns Hardhat's behavior with the rest of the tooling ecosystem.
This should only improve the error messages that you see, and doesn't require any update, unless you are manually interacting with JSON-RPC responses.
Proxies support in the --gas-stats feature
Hardhat now classifies gas stat entries based on both the implementation contract and the proxy (or proxies) in front of it. For example, a proxy forwarding to a Vault contract is now reported as gas spent in Vault via the proxy.
Previously, that same call would have reported all the gas under the proxy, hiding which implementation actually did the work.
The Hardhat version is now included in the BuildInfo files of production builds
If you compile your project with the production Build Profile, a new toolVersions field containing the Hardhat version will be added to your Build Info files. This can help you reproduce your builds in the future.
You can enable this functionality in other build profiles with the flag toolVersionsInBuildInfo.
Smaller Build Info output files by using a better default output selector
Building your contracts with this version of Hardhat will lead to a significantly smaller artifacts/ folder, in some cases around 50% smaller.
User migration guide
To update to this new version of Hardhat, please follow this guide.
Replace network.connect() with network.create().
Replace network.connect(...) calls with network.create(...). Everything else keeps working as expected.
Build ids may change even when bytecode is identical
As a result of adding the toolVersions field and changing the default output selector, your build ids can change, despite the compilation output's bytecode being identical.
If you need to get the exact same version, please downgrade to the previous version. In future production builds, you'll always know exactly which version was used, so this should be simpler.
Plugin Migration Guide: Adapting to splitTestsCompilation: false
This guide covers what splitTestsCompilation: false changes for plugin authors and how to adapt.
Configuration
The field is accepted in all object-typed Solidity user configs (SingleVersionSolidityUserConfig, MultiVersionSolidityUserConfig, BuildProfilesSolidityUserConfig). String and string-array configs always resolve to false.
The resolved value is available at hre.config.solidity.splitTestsCompilation.
Build System (hre.solidity)
scope: "tests" is rejected when splitTestsCompilation is false
When splitTestsCompilation is false, tests are compiled together with contracts under scope: "contracts". Using scope: "tests" is a logic error and throws a HardhatError with descriptor SOLIDITY.SPLIT_TESTS_COMPILATION_DISABLED in the following APIs:
hre.solidity.getRootFilePaths({ scope: "tests" })hre.solidity.build(rootFiles, { scope: "tests" })hre.solidity.getCompilationJobs(rootFiles, { scope: "tests" })hre.solidity.emitArtifacts(compilationJob, compilerOutput, { scope: "tests" })hre.solidity.cleanupArtifacts(rootFiles, { scope: "tests" })
Exception: hre.solidity.getArtifactsDirectory("tests") does not throw. It returns the main artifacts path (same as "contracts"), since it is a read-only query with no side effects.
getRootFilePaths({ scope: "contracts" })
When splitTestsCompilation is false, this returns all build roots — contract roots, test roots, and npm roots — together.
When splitTestsCompilation is true, it returns contract roots and npm roots only (unchanged).
getArtifactsDirectory(scope)
splitTestsCompilation
| scope: "contracts"
| scope: "tests"
|
|---|---|---|
false
| artifactsPath
| artifactsPath
|
true
| artifactsPath
| cachePath/test-artifacts
|
File classification is unchanged
hre.solidity.getScope(fsPath) continues to classify files as "contracts" or "tests" based on path and suffix rules. Use this API to distinguish contract files from test files when processing mixed sets.
cleanupArtifacts()
When splitTestsCompilation is false:
- Cleanup operates on the main artifacts directory.
- Duplicate contract-name detection runs across the mixed contract/test artifact set.
onCleanUpArtifactsreceives the mixed contract/test artifact set, so if you are hooking into it, you may need to adapt your Hook Handler. See below.
Artifacts
When splitTestsCompilation is false, both contract and test artifacts live under the same paths.artifacts directory. This means:
getAllArtifactPaths()includes test artifacts.getAllFullyQualifiedNames()includes test artifacts.- Bare-name lookup can become ambiguous if a test contract and a source contract share the same name. Ambiguous names resolve to
neverin the generatedartifacts.d.ts. Users hitting a collision should switch to fully qualified names (e.g."contracts/Foo.sol:Foo"instead of"Foo"); this affects APIs likehardhat-ethers'sgetContractFactory/getContractAt/deployContract,hardhat-viem'sdeployContract/getContractAt,hardhat-verify's automatic contract inference, andhardhat-ignition's artifact resolution. - Fully qualified name lookup continues to work without ambiguity.
- Test roots do not get per-source
artifacts.d.tsfiles. Only contract roots emit TypeScript declarations. They are not part of theArtifactMapinterface fromhardhat/types/artifacts.- This means that test contracts aren't part of the autocompletion in the
ethersandviemplugins.
- This means that test contracts aren't part of the autocompletion in the
Plugins using hre.artifacts must no longer assume that "artifacts path" means "contracts only."
Filtering test artifacts
You can take a look at the hardhat-typechain plugin to understand how to filter out the test artifacts.
Build Task (hardhat build / hardhat compile)
When splitTestsCompilation is false
The build task uses a single compilation pass under scope: "contracts".
| Scenario | Behavior |
|---|---|
| Full build (no flags, no files) | Compiles all contracts and tests. Runs cleanup. Regenerates top-level artifacts.d.ts. Fires onCleanUpArtifacts.
|
Explicit files
| Partial build of exactly those files. No cleanup. No artifacts.d.ts regeneration. No onCleanupArtifacts.
|
--no-tests (no files)
| Partial build of contract roots only. No cleanup. No artifacts.d.ts regeneration. No onCleanupArtifacts.
|
--no-contracts (no files)
| Partial build of test roots only. No cleanup. No artifacts.d.ts regeneration. No onCleanupArtifacts.
|
files + compatible flag (e.g. contract files + --no-tests)
| Partial build of the provided files. No cleanup. No artifacts.d.ts regeneration. No onCleanupArtifacts.
|
files + incompatible flag (e.g. test files + --no-tests)
| Throws HardhatError with descriptor SOLIDITY.INCOMPATIBLE_FILES_WITH_BUILD_FLAGS.
|
Important: --no-tests and --no-contracts behave as synthetic partial builds when splitTestsCompilation is false. This is different from splitTestsCompilation: true, where --no-tests runs a full contracts build with cleanup. Plugins that depend on cleanup running after --no-tests should account for this.
When splitTestsCompilation is true
Current two-pass behavior is preserved. --no-tests and --no-contracts each skip one full pass with cleanup.
Return value
Both modes return:
{
contractRootPaths: string[];
testRootPaths: string[];
}The arrays reflect the roots actually built, partitioned using getScope().
Plugin pattern for calling build
If your plugin calls build with noTests: true, update it to branch on the config. In unified mode (splitTestsCompilation: false), passing noTests: true turns the build into a partial build — cleanup and artifacts.d.ts regeneration are skipped. Since tests are compiled in the same pass as contracts anyway, there is no performance benefit to excluding them, and the plugin loses full-build semantics it likely depends on.
// Before
await hre.tasks.getTask("build").run({ noTests: true });
// After: only skip tests when they live in a separate compilation pass.
const noTests = hre.config.solidity.splitTestsCompilation;
await hre.tasks.getTask("build").run({ noTests });Solidity Hooks
build hook
When splitTestsCompilation is false, full builds call the hook once with scope: "contracts" and a mixed set of contract and test roots. Plugins that need contract-only behavior must filter per file with getScope().
preprocessProjectFileBeforeBuilding
The same compilation may include both contract and test files. Plugins can distinguish with context.solidity.getScope(fsPath).
preprocessSolcInputBeforeBuilding
solcInput.sources may contain both contract and test sources together when splitTestsCompilation is false.
onCleanUpArtifacts
When splitTestsCompilation is false, this hook only fires during full builds (not partial builds from --no-tests, --no-contracts, or explicit files). It receives mixed contract/test artifact paths. Take a look at the hardhat-typechain plugin for an example of how to filter out test artifacts.
Unchanged hooks
downloadCompilers, getCompiler, invokeSolc, readSourceFile, and readNpmPackageRemappings are not affected.
Full list of changes
-
#8116
88787e1Thanks @kanej! - AddgetOrCreateto the network API -
#8127
353cf86Thanks @alcuadrado! - Make the split of contracts and solidity tests compilation optional, and controlled with a newsplitTestsCompilationconfig field. -
#8105
00e9695Thanks @marianfe! - Add Solidity 0.8.34 to the default EVM targets table (osaka) (#8105) -
#8108
5404ac8Thanks @schaable! - Display contract runtime bytecode size in the gas stats table and JSON output -
#8104
e27a7adThanks @ChristopherDedominici! - Use code 3 for JSON-RPC revert error codes to align with standard node behavior and preserve error causes in viem/ethers. -
#8103
14b335aThanks @kanej! - Improved network handler performance through additional metadata to allow early skipping (#8103) -
#8148
49ec5d0Thanks @alcuadrado! - Don't report HardhatErrors that aren't bugs -
#8102
d5f8394Thanks @kanej! - Improved performance of network handler initialization (#8102) -
#8141
63c68c1Thanks @ChristopherDedominici! - Added support for EDR structured Solidity test cheatcode errors. -
#8123
cf3933bThanks @alcuadrado! - Add atoolVersionsInBuildInfosetting to the Solidity config, which istrueby default in theproductionbuild profile. When enabled, the version of Hardhat is included in the Build Info files.NOTE: This change can lead to build info ids changing despite the compilation output's bytecodes being identical, especially when using the
productionbuild profile. -
#8143
f74cec9Thanks @ChristopherDedominici! - Update.gitignorefiles in the sample projects to ignore snapshots and env files. -
#8096
7fb721bThanks @alcuadrado! - [chore] Move to packages/ folder. -
#8116
88787e1Thanks @kanej! - Deprecate thehre.network.connect()method in favour ofhre.network.create(), exactly the same method but more clearly indicating that it will create a new connection. -
#8119
ff5a97eThanks @schaable! - Show proxy chain information in --gas-stats and --gas-stats-json output -
#8114
6eeb144Thanks @ChristopherDedominici! - Updated forking configurations to support number and bigint. -
#8121
0f1038cThanks @alcuadrado! - Update the default outputSelection setting of solc to decrease the artifacts size.NOTE: This change can lead to build info ids changing, despite compilation output's bytecodes being identical.
-
#8122
edfa548Thanks @alcuadrado! - Optimizehre.artifacts.artifactExists() -
#8115
935a043Thanks @ChristopherDedominici! - Breaking change: removedtimeoutoption for Solidity tests inhardhat.config.tsfile. -
#8120
688870cThanks @alcuadrado! - Fix remappings duplication -
Updated dependencies:
- @nomicfoundation/hardhat-errors@3.0.11
- @nomicfoundation/hardhat-utils@4.0.3
- @nomicfoundation/hardhat-vendored@3.0.2
- @nomicfoundation/hardhat-zod-utils@3.0.4
💡 The Nomic Foundation is hiring! Check our open positions.