Skip to content

Migrate from Foundry

Hardhat 3 is a full replacement for Foundry: it runs Solidity tests, understands remappings.txt, and can build contracts laid out the way Foundry expects.

On top of that compatibility, Hardhat 3 brings capabilities Foundry doesn’t:

  • An extensible stack — with a plugin system and integration with the TypeScript ecosystem, you can bring your own opinions to deployment, verification and other workflows.
  • Dependencies managed with npm — Hardhat supports both git submodules and npm dependencies. You have the option to keep your existing lib/ git submodules, and you can also install dependencies as npm packages to get lockfiles, semver, and native support for transitive dependencies.
  • Multichain testing — run the same Solidity tests against a simulated Ethereum L1 or OP Stack chain by switching the chain type, so you catch chain-specific behavior before you deploy.
  • The right test for the job — write fast Solidity unit tests for contract logic, exactly as you do today, and reach for TypeScript for integration tests (with viem or ethers) when you need to test against a full network, or to build custom scripts in a full-featured language.

This guide will take you, step by step, through migrating a Foundry project to Hardhat 3. We start with a clean Hardhat config, alongside your existing foundry.toml, and migrate one capability at a time.

Before installing Hardhat 3, prepare your project.

  1. Check Node.js version

    Make sure you have installed Node.js v22.13.0 or later:

    Terminal window
    node --version
  2. Initialize an npm project

    If your repo doesn’t already have a package.json, create one and set it to ESM:

    Terminal window
    npm init -y
    npm pkg set type=module

    Add node_modules/ to your .gitignore if it isn’t there already.

  3. Keep your Foundry setup for reference

    Leave foundry.toml, remappings.txt, and the lib/ directory in place during the migration. Hardhat will pick up your remappings.txt automatically, and you can remove lib/ if you choose to move your dependencies to npm.

  4. (Optional) Add or adapt your tsconfig.json

    We recommend TypeScript over JavaScript for tasks, scripts and deployments. Add or edit your tsconfig.json so that compilerOptions.module is set to an ESM-compatible value like “node20”. A minimal tsconfig.json would be:

    tsconfig.json
    {
    "compilerOptions": {
    "lib": ["es2023"],
    "module": "node20",
    "target": "es2023",
    "skipLibCheck": true,
    "outDir": "dist",
    "types": ["node"]
    }
    }

With your npm project ready, you can install Hardhat 3.

  1. Install Hardhat 3

    Terminal window
    npm add --save-dev hardhat
  2. Create an empty config file

    Create a hardhat.config.ts file with the following content:

    hardhat.config.ts
    import { defineConfig } from "hardhat/config";
    export default defineConfig({});
  3. Run the help command

    Verify that Hardhat 3 is working:

    Terminal window
    npx hardhat --help

Port the compiler settings and project layout from foundry.toml so Hardhat finds and builds the same files.

  1. Add a solidity entry

    Copy your solc version, optimizer settings, via_ir and evm_version from foundry.toml into the solidity entry. For example:

    hardhat.config.ts
    import { defineConfig } from "hardhat/config";
    export default defineConfig({
    solidity: {
    version: "0.8.28",
    settings: {
    optimizer: {
    enabled: true,
    runs: 200,
    },
    evmVersion: "cancun",
    viaIR: false,
    },
    },
    });
  2. Point Hardhat at your existing source and test directories

    Foundry’s defaults are src/ for contracts and test/ for tests. Hardhat defaults to contracts/ and test/, so override the sources path:

    hardhat.config.ts
    import { defineConfig } from "hardhat/config";
    export default defineConfig({
    paths: {
    sources: "./src",
    },
    solidity: {
    /* ... */
    },
    });
  3. Compile your contracts

    Run the build task to verify that the config is working:

    Terminal window
    npx hardhat build

For the full configuration reference, see the configuration reference.

Foundry installs dependencies as git submodules under lib/ and exposes them through remappings.txt. Hardhat supports that approach without changes, but also offers the option to use npm packages as dependencies rather than git submodules.

  1. Install npm equivalents of your lib/ dependencies

    Most common dependencies are published to npm. For example:

    Terminal window
    npm add --save-dev @openzeppelin/contracts

    For forge-std, install it directly from GitHub via your package manager:

    Terminal window
    npm add --save-dev 'github:foundry-rs/forge-std#v1.9.7'
  2. Reconcile remappings

    Hardhat 3 reads remappings.txt files automatically, including the ones inside npm packages and git submodules, so your existing entries keep working. Once a dependency is installed via npm, you can either:

    • Keep your existing imports by updating the remapping target within remappings.txt to point at the npm package (e.g. @openzeppelin/contracts/=node_modules/@openzeppelin/contracts/), or
    • Drop the remapping entirely and rewrite your import statements to use the npm package path (e.g. import "@openzeppelin/contracts/access/Ownable.sol") — Hardhat resolves npm imports natively, no remapping required.

    If your project relies on forge-generated remappings (no remappings.txt is checked in), install the @nomicfoundation/hardhat-foundry plugin and add it to your plugins array.

  3. Optionally remove lib/ once everything builds

    Re-run hardhat build to confirm the npm-resolved dependencies work. If all of your dependencies have been migrated to npm, you can delete lib/ and the corresponding .gitmodules entries.

Hardhat 3’s Solidity testing is designed for compatibility with Foundry test suites. Solidity tests, cheatcodes, and forge-std are supported out of the box — no plugin required.

  1. Confirm your test layout is picked up

    By default, Hardhat treats every .sol file under test/ as a test file, and any .t.sol file under src/ (or your configured sources directory). A contract is treated as a test contract if it has at least one test* function, and setUp() runs before each test — the same conventions as Foundry. If your tests live somewhere else, override paths.tests.solidity:

    hardhat.config.ts
    export default defineConfig({
    paths: {
    tests: {
    solidity: "./test",
    },
    },
    });
  2. Run a single test file

    Start by running one test file to confirm everything compiles and executes:

    Terminal window
    npx hardhat test solidity test/SomeTest.t.sol

    Then run the full Solidity suite:

    Terminal window
    npx hardhat test solidity

    hardhat test (without solidity) runs every test runner registered in your config (e.g. node:test); if you only have Solidity tests today, the two are equivalent.

  3. Port your test execution settings

    Translate settings from [profile.default] and [fuzz] / [invariant] in foundry.toml into the test.solidity entry:

    hardhat.config.ts
    import { defineConfig } from "hardhat/config";
    export default defineConfig({
    test: {
    solidity: {
    isolate: true,
    fuzz: {
    runs: 256,
    },
    invariant: {
    runs: 256,
    depth: 15,
    },
    },
    },
    });

    See the Solidity tests configuration reference for the full list of options, including from, txOrigin, blockTimestamp, fork settings, and the fsPermissions allowlist that replaces Foundry’s fs_permissions.

  4. Enable ffi and filesystem access if your tests need them

    These are off by default for security reasons. If you use vm.ffi, set test.solidity.ffi: true. If you use vm.readFile/vm.writeFile, declare the allowed paths under test.solidity.fsPermissions.

  5. Replace anvil with the in-process network (or hardhat node)

    Solidity tests run against an in-process EDR-powered network, so you don’t need a separate anvil instance to execute them — including for fork tests, which are configured under test.solidity.fork. If you previously ran anvil for ad-hoc interaction (for example to attach a script or external client), run a JSON-RPC node with:

    Terminal window
    npx hardhat node

Hardhat doesn’t have a direct equivalent of forge script. For deployments, we recommend Hardhat Ignition, which is declarative and handles resumability, idempotency, and verification. For one-off scripts (admin actions, data migrations), define a Hardhat task and call your contracts through a network connection:

hardhat.config.ts
import { defineConfig, task } from "hardhat/config";
const deployCounter = task("deploy-counter", "Deploys the Counter contract")
.setInlineAction(async (_taskArgs, hre) => {
const { viem } = await hre.network.create();
const counter = await viem.deployContract("Counter");
console.log("Counter deployed at:", counter.address);
})
.build();
export default defineConfig({
tasks: [deployCounter],
});

The viem (or ethers) helpers come from a toolbox plugin; install whichever you prefer and add it to plugins in your config.

If your migration is blocked by a missing feature, plugin, or cheatcode, please let us know in this issue.