Skip to content

Amsterdam hardfork support

Hardhat provides experimental support for the upcoming Amsterdam execution-layer hardfork. It lets you start testing your contracts against Amsterdam’s new behavior before it activates on Ethereum Mainnet.

Amsterdam is not enabled by default: osaka remains Hardhat’s default and latest stable hardfork. You have to opt in explicitly, see enabling Amsterdam.

We are implementing Amsterdam’s EIPs incrementally, and will add more as the hardfork is finalized in the run-up to launch.

The following Amsterdam EIPs are currently supported:

Set hardfork to "amsterdam" on a simulated network with the l1 or generic chain type:

hardhat.config.ts
import { defineConfig } from "hardhat/config";
export default defineConfig({
networks: {
edrAmsterdam: {
type: "edr-simulated",
chainType: "l1",
hardfork: "amsterdam",
},
},
});

You can then use the new network as the default in scripts and tests with the --network flag:

Terminal window
npx hardhat run scripts/example.ts --network edrAmsterdam

Or you can name it explicitly when creating a network:

import { network } from "hardhat";
const connection = await network.create({ network: "edrAmsterdam" });

Under EIP-7708, ETH transfers emit a log with the same shape as an ERC-20 Transfer event:

  • address: the system address 0xfffffffffffffffffffffffffffffffffffffffe
  • topics[0]: keccak256("Transfer(address,address,uint256)")
  • topics[1]: the sender
  • topics[2]: the recipient
  • data: the amount in wei

This means plain ETH transfers — which previously produced no logs — now emit one. The following is a minimal script that sends 1 ETH and prints the resulting log using viem:

scripts/transfer-logs.ts
import { network } from "hardhat";
import {
decodeEventLog,
formatEther,
getAddress,
parseAbiItem,
toEventSelector,
} from "viem";
// EIP-7708: value-transferring operations emit a log from this system address
const LOG_EMITTING_SYSTEM_ADDRESS =
"0xfffffffffffffffffffffffffffffffffffffffe";
// topics[0] is keccak256("Transfer(address,address,uint256)")
const transferEvent = parseAbiItem(
"event Transfer(address indexed from, address indexed to, uint256 value)",
);
const TRANSFER_TOPIC = toEventSelector(transferEvent);
const { viem } = await network.create({ network: "edrAmsterdam" });
const [sender, recipient] = await viem.getWalletClients();
const publicClient = await viem.getPublicClient();
const hash = await sender.sendTransaction({
to: recipient.account.address,
value: 10n ** 18n, // 1 ETH
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
const transferLogs = receipt.logs.filter(
(log) =>
getAddress(log.address) === getAddress(LOG_EMITTING_SYSTEM_ADDRESS) &&
log.topics[0] === TRANSFER_TOPIC,
);
console.log("ETH transfer logs emitted by the system address:\n");
for (const log of transferLogs) {
const { args } = decodeEventLog({
abi: [transferEvent],
data: log.data,
topics: log.topics,
});
console.log({
emittedBy: getAddress(log.address),
from: args.from,
to: args.to,
value: `${formatEther(args.value)} ETH`,
});
}

Running it prints a log emitted by the system address, with the from and to addresses derived from its topics and the transferred amount from its data:

Terminal window
npx hardhat run scripts/transfer-logs.ts