Skip to content

Simulated Networks by EDR

This page provides a reference for Hardhat’s simulated networks, powered by the Ethereum Development Runtime (EDR). Here you’ll find detailed information about all available features and behaviors of EDR-based local blockchains.

If you’re looking for a conceptual overview, see the explanation instead.

Simulated networks include features specifically designed for testing and debugging smart contracts. Unlike production blockchains, EDR is aware of your Solidity code and understands what’s running.

EDR also aims to precisely simulate the blockchains you’ll deploy your contracts to, both when running TypeScript and Solidity tests.

EDR has native support for simulating different types of blockchains. You can specify the chain type in your Network Config:

hardhat.config.ts
// ... imports ...
export default defineConfig({
// ... other config ...
networks: {
// ... other networks ...
hardhatMainnet: {
type: "edr-simulated",
chainType: "l1", // Ethereum mainnet and its testnets
},
hardhatOptimism: {
type: "edr-simulated",
chainType: "op", // OP Mainnet and its testnets
},
other: {
type: "edr-simulated",
chainType: "generic", // Default value, which is a permissive approximation to mainnet
},
},
});

When a transaction or call fails, EDR provides Solidity stack traces. These traces include both the TypeScript test code where the transaction or call originated and the full Solidity call stack, making it easy to understand exactly where and why your code failed.

You can use console.log() in your Solidity code to print debugging information. Simply import hardhat/console.sol and call it like you would in JavaScript:

import "hardhat/console.sol";
contract MyContract {
function myFunction(uint256 value) public {
console.log("Processing value: %d", value);
}
}

The logs appear in your test output, helping you understand what’s happening inside your contracts. Learn more in the console.log reference.

EDR detects common error situations and reports clear error messages. For example, it will tell you if you’re:

  • Calling a non-payable function with ETH
  • Sending ETH to a contract without a payable fallback or receive function
  • Calling a non-existent function when there’s no fallback function
  • Calling a function with incorrect parameters
  • Calling an external function that doesn’t return the right amount of data
  • Calling an external function on a non-contract account
  • Failing to execute an external call because of its parameters (for example, trying to send too much ETH)
  • Calling a library without DELEGATECALL
  • Incorrectly calling a precompiled contract
  • Trying to deploy a contract that exceeds the bytecode size limit imposed by EIP-170

EDR provides additional JSON-RPC methods that are useful for development and testing. These methods allow you to do things like manipulate the blockchain state or control mining behavior.

We don’t recommend using these methods directly. Instead, use Hardhat’s network helpers, which wrap them in a more user-friendly way.

The initial state of a simulated network depends on whether you’re using forking mode and your Network Config.

By default, simulated networks start with an empty blockchain that includes:

  • Standard precompiled contracts (like ecrecover, sha256, etc.)
  • Any predeployed contracts specified by your chain type (for example, Optimism’s predeploys)
  • The accounts configured in your Network Config, each with the specified balance and no code

In forking mode, the simulated network starts as a copy of a remote blockchain at a specific block. The state is lazy-loaded from the remote network as needed, which means you don’t have to wait for the entire blockchain to download.

When forking, there are some differences from the remote network:

  • Accounts in your Network Config have their balance and code overwritten (removing EIP-7702 delegations if present)
  • The chainId is taken from your Network Config, not the remote chain, for security reasons (though this can be configured)

You can configure forking in your Network Config like this:

hardhat.config.ts
// ... imports ...
export default defineConfig({
// ... other config ...
networks: {
// ... other networks ...
mainnetFork: {
type: "edr-simulated",
forking: {
url: "https://mainnet.infura.io/v3/YOUR_API_KEY",
blockNumber: 14390000, // optional
},
},
},
});

EDR supports different mining modes that control when and how new blocks are created.

By default, automine is enabled. In this mode, a new block is mined immediately whenever a transaction is received. This makes tests fast because you don’t have to wait for blocks to be mined.

You can enable interval mining to mine new blocks periodically, regardless of whether there are pending transactions. This is useful for testing time-dependent contract behavior.

You can configure interval mining to use a fixed interval or a random interval between two values:

hardhat.config.ts
// ... imports ...
export default defineConfig({
// ... other config ...
networks: {
// ... other networks ...
intervalMined: {
type: "edr-simulated",
mining: {
auto: false, // disable automining
interval: 5000, // mine a new block every 5 seconds
},
},
},
});

Or with a random interval:

hardhat.config.ts
// ... imports ...
export default defineConfig({
// ... other config ...
networks: {
// ... other networks ...
intervalMined: {
type: "edr-simulated",
mining: {
auto: false, // disable automining
interval: [3000, 6000], // mine blocks at random intervals between 3-6 seconds
},
},
},
});

If you disable both automine and interval mining, transactions will accumulate in the mempool without being mined. You can manually mine blocks using the evm_mine RPC method:

scripts/manual-mining.ts
import { network } from "hardhat";
const { networkHelpers } = await network.connect();
await networkHelpers.mine();

This is useful when you need precise control over when blocks are mined.

You can use automine and interval mining together. In this case, blocks are mined both when transactions arrive and at regular intervals.

You can also decide not to use either mode and rely solely on manual mining.

When automine is disabled, transactions are added to the mempool before being included in a block. By default, EDR’s mempool follows the same rules as Geth:

  • Transactions with a higher gas price are included first
  • If two transactions offer the same total fees, the one received first is included first
  • Invalid transactions (for example, with an incorrect nonce) are dropped

You can configure the mempool to use FIFO (first-in, first-out) instead of priority ordering. In FIFO mode, transactions are included in blocks in the exact order they were received:

hardhat.config.ts
// ... imports ...
export default defineConfig({
// ... other config ...
networks: {
// ... other networks ...
fifoNetwork: {
type: "edr-simulated",
mining: {
auto: false,
interval: 2000,
mempool: {
order: "fifo",
},
},
},
},
});

This is useful for recreating blocks from other networks where the transaction order is known.

To learn more about configuring simulated networks, read the configuration reference.