github NomicFoundation/hardhat hardhat@3.4.0
Hardhat v3.4.0

7 hours ago

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.
  • onCleanUpArtifacts receives 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 never in the generated artifacts.d.ts. Users hitting a collision should switch to fully qualified names (e.g. "contracts/Foo.sol:Foo" instead of "Foo"); this affects APIs like hardhat-ethers's getContractFactory / getContractAt / deployContract, hardhat-viem's deployContract / getContractAt, hardhat-verify's automatic contract inference, and hardhat-ignition's artifact resolution.
  • Fully qualified name lookup continues to work without ambiguity.
  • Test roots do not get per-source artifacts.d.ts files. Only contract roots emit TypeScript declarations. They are not part of the ArtifactMap interface from hardhat/types/artifacts.
    • This means that test contracts aren't part of the autocompletion in the ethers and viem plugins.

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 88787e1 Thanks @kanej! - Add getOrCreate to the network API

  • #8127 353cf86 Thanks @alcuadrado! - Make the split of contracts and solidity tests compilation optional, and controlled with a new splitTestsCompilation config field.

  • #8105 00e9695 Thanks @marianfe! - Add Solidity 0.8.34 to the default EVM targets table (osaka) (#8105)

  • #8108 5404ac8 Thanks @schaable! - Display contract runtime bytecode size in the gas stats table and JSON output

  • #8104 e27a7ad Thanks @ChristopherDedominici! - Use code 3 for JSON-RPC revert error codes to align with standard node behavior and preserve error causes in viem/ethers.

  • #8103 14b335a Thanks @kanej! - Improved network handler performance through additional metadata to allow early skipping (#8103)

  • #8148 49ec5d0 Thanks @alcuadrado! - Don't report HardhatErrors that aren't bugs

  • #8102 d5f8394 Thanks @kanej! - Improved performance of network handler initialization (#8102)

  • #8141 63c68c1 Thanks @ChristopherDedominici! - Added support for EDR structured Solidity test cheatcode errors.

  • #8123 cf3933b Thanks @alcuadrado! - Add a toolVersionsInBuildInfo setting to the Solidity config, which is true by default in the production build 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 production build profile.

  • #8143 f74cec9 Thanks @ChristopherDedominici! - Update .gitignore files in the sample projects to ignore snapshots and env files.

  • #8096 7fb721b Thanks @alcuadrado! - [chore] Move to packages/ folder.

  • #8116 88787e1 Thanks @kanej! - Deprecate the hre.network.connect() method in favour of hre.network.create(), exactly the same method but more clearly indicating that it will create a new connection.

  • #8119 ff5a97e Thanks @schaable! - Show proxy chain information in --gas-stats and --gas-stats-json output

  • #8114 6eeb144 Thanks @ChristopherDedominici! - Updated forking configurations to support number and bigint.

  • #8121 0f1038c Thanks @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 edfa548 Thanks @alcuadrado! - Optimize hre.artifacts.artifactExists()

  • #8115 935a043 Thanks @ChristopherDedominici! - Breaking change: removed timeout option for Solidity tests in hardhat.config.ts file.

  • #8120 688870c Thanks @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.


Don't miss a new hardhat release

NewReleases is sending notifications on new releases.