How to Fork Ethereum Mainnet with Hardhat and Impersonate Accounts on Mainnet.

How to Fork Ethereum Mainnet with Hardhat and Impersonate Accounts on Mainnet.

Here is a guide to fork mainnet using hardhat, how to impersonate accounts with hardhat, simulating the the same state as mainnet, but it will work as a local development network. That way you can interact with deployed protocols and test complex interactions locally. In this article, you will learn to fork mainnet and also impersonate account.

Let's get started

What is Hardhat Forking?

Hardhat is a development environment for Ethereum software. It consists of different components for editing, compiling, debugging and deploying your smart contracts and dApps, all of which work together to create a complete development environment.

Hardhat forking is creating a local replica of an existing blockchain (like Ethereum's mainnet) for development purposes. This allows smart contract developers to:

  • Test and debug contracts against a real-world state without affecting the actual blockchain.

  • Interact with existing contracts deployed on the mainnet in a safe environment.

It's like creating a "practice" version of the mainnet for testing and experimentation.

Why do we Fork Mainnet?

  • It's used to interact with contract deployed on the mainnet

  • Used for impersonation of accounts. etc

Tools needed for Mainnet Forking?

  • Hardhat

  • Command Line Interface (CLI)

  • Remote Procedure Call (RPC) node (It can be gotten from either Alchemy, QuickNode, Ankr etc).

Set Up Hardhat Project

We will start by installing node, create a folder, install hardhat package and other items we would need.

Install Node and npm

In your cli, create a new folder

mkdir mainnet-hardhat-forking

we move into the folder

cd mainnet-hardhat-forking

We initialise npm in the created project folder, we proceed to install Hardhat and dotenv package using npm in the terminal:

we use dotenv package to manage our env files (environmental variables).

npm init --y && npm install --save-dev hardhat && npm i --save-dev dotenv

After running the the commands above, your package.json file you be like this.

then we proceed to initialise hardhat in our directory

npx hardhat init

hardhat installation interface

Select create a Typescript project with your keyboard and hit enter for every prompts, to create a fresh hardhat project.

When its done installing you will see an image like the one below

To test all your installation is correct and all working run the command below:

npx hardhat test

You should get a response like this:

Get an RPC node key, to do this its advised according to hardhat documentation to use a Full Archive node with both Infura and Alchemy provides, but i will only be talking on how to create API from Alchemy.

  1. Create an alchemy account: Create your free alchemy account at https://auth.alchemy.com/signup and confirm your email.

https://auth.alchemy.com/signup

2. Create an app: In your alchemy dashboard, click on + CREATE APP button. This brings a popup to create a new app. Fill in the name and description in the form and click CREATE APP button. Example:
Name: mainnet-fork
Description: Description of what your app does so you can keep track.
Chain: Ethereum (default selection)
Network: Mainnet (default selection)

3. View key: Once your new app is created, the pop disappears. The app appeared under the Personal Apps table. Locate the newly created app in the table and click on the view key button. Copy the API KEY.

create an env file to store your credentials

touch .env

the paste the code line below in it and also copy

MAINET_ALCHEMY_API_KEY_URL="your alchemy http key here"

Modify your hardhat.config.ts file to this

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import dotenv from "dotenv";

dotenv.config();

const MAINET_ALCHEMY_API_KEY_URL = process.env.MAINET_ALCHEMY_API_KEY_URL;

if (!MAINET_ALCHEMY_API_KEY_URL) {
  console.log("Please include MAINET_ALCHEMY_API_KEY_URL");
  process.exit(1);
}

const config: HardhatUserConfig = {
  solidity: "0.8.20",
  networks: {
    hardhat: {
      forking: {
        //@ts-ignore
        url: MAINET_ALCHEMY_API_KEY_URL,
        blockNumber: 19257831,
      },
    },
  },
};

export default config;

In Your terminal start your local node which would be simulating with the mainnet

npx hardhat node

mainnet forking using hardhat

To confirm that we have forked the latest block from Mainnet, you can send the following curl request into the terminal:

curl --location --request POST 'localhost:8545/' \ 
--header 'Content-Type: application/json' \ 
--data-raw '{ 
    "jsonrpc":"2.0", 
    "method":"eth_blockNumber", 
    "params":[], 
    "id":83 
    }'

You should then receive a response like the one below but your result will be different:

JSON

{"jsonrpc":"2.0","id":83,"result":"0xe2df84"}

The result value is the hexadecimal number of the latest Mainnet block. By using a Hex converter, you take the block number and reference it with Etherscan to validate that the timestamp is recent and you can also pick the decimal converted hex and replace in the blockNumber in your hardhat.config.ts .

Congratulations you have successfully forked mainnet with hardhat.

Now lets impersonate account and lets send some USDC from someone wallet and mainnet.

Hardhat Network allows you to impersonate any address. This lets you send transactions from that account even if you don't have access to its private key.

The easiest way to do this is with the ethers.getImpersonatedSigner method, which is added to the ethers object by the hardhat-ethers plugin:

const impersonatedSigner = await ethers.getImpersonatedSigner("0x1234567890123456789012345678901234567890");
await impersonatedSigner.sendTransaction(...);

Alternatively, you can use the impersonateAccount helper and then obtain the signer for that address:

const helpers = require("@nomicfoundation/hardhat-toolbox/network-helpers");

const address = "0x1234567890123456789012345678901234567890";
await helpers.impersonateAccount(address);
const impersonatedSigner = await ethers.getSigner(address);

delete the Lock.sol in your contract folder, Lock.ts in test folder and also deploy.ts in scripts folder.

create a new file in contract folder: IERC20.sol, because i'm interacting with ERC20 token which is USDC, i'm creating an interface for me to interact with the USDC Contract.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

interface IERC20{
    function approve(address _spender, uint _value) external;
    function balanceOf(address who) external view returns(uint256 balance);
    function transfer(address recipient, uint256 amount) external returns (bool);
}

compile it to check for errors

 npx hardhat compile

create a file in your scripts folder in your directory and create a file sendUsdc.ts

import { ethers } from "hardhat";
const helpers = require("@nomicfoundation/hardhat-toolbox/network-helpers");

async function sendUsdc() {
  const WHALEADDY = "0xf584f8728b874a6a5c7a8d4d387c9aae9172d621";
  const UsdcContractAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";

  await helpers.impersonateAccount(WHALEADDY);
  const impersonatedSigner = await ethers.getSigner(WHALEADDY);

  const usdcContract = await ethers.getContractAt(
    "IERC20",
    UsdcContractAddress
  );

  const [otherAccount] = await ethers.getSigners();

  console.log("Other Account Address : ", otherAccount.address);

  const amountToTransfer = ethers.parseUnits("10000", 6);

  let whaleBalance = await usdcContract.balanceOf(WHALEADDY);
  let otherBalance = await usdcContract.balanceOf(otherAccount.address);

  console.log(
    "Whale Account Balance before transaction : ",
    ethers.formatUnits(whaleBalance, 6)
  );

  console.log(
    "Other Account Balance before transaction : ",
    ethers.formatUnits(otherBalance, 6)
  );

  await usdcContract
    .connect(impersonatedSigner)
    .transfer(otherAccount.address, amountToTransfer);

  whaleBalance = await usdcContract.balanceOf(WHALEADDY);
  otherBalance = await usdcContract.balanceOf(otherAccount.address);

  console.log(
    "Whale Account Balance after transaction  : ",
    ethers.formatUnits(whaleBalance, 6)
  );

  console.log(
    "Other Account Balance before transaction : ",
    ethers.formatUnits(otherBalance, 6)
  );
}

sendUsdc()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

Dont forget your local node is still on, then run the following command to run the script above

npx hardhat run scripts/sendUsdc.ts

You will get the following output in your terminal.

Other Account Address : 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

Whale Account Balance before transaction : 17542684.255046

Other Account Balance before transaction : 0.0

Whale Account Balance after transaction : 17492684.255046

Other Account Balance before transaction : 50000.0

With aid of Hardhat forking you have created a local replica of a real blockchain, like Ethereum's mainnet. This replica, entirely separate from the actual network which can be used test your contracts in a safe environment, eliminating the associated costs of deploying on the actual mainnet and also leverage historical data and test wallets to gain valuable insights into how your contract or dApp will function on the mainnet.

This approach provides a controlled and efficient way to test and refine your smart contracts before deploying them on the actual mainnet.

Here is the github repo

I'll be publishing more articles soon on interacting with UNISWAP V2 different Swap functions, make sure to check back.

Big Shout out to WEB3BRIDGE for the work they do in onboarding web3 developers.

Resources:

https://github.com/Oluwatobilobaoke/hardhat-mainnet-forking

https://hardhat.org/hardhat-runner/docs/getting-started#quick-start

https://docs.alchemy.com/docs/how-to-fork-ethereum-mainnet

https://www.youtube.com/watch?v=mp758KKrCgc&list=PLXiAtWDhSlIsZi_H6CjdOt5l863P_E1NJ&index=11

https://medium.com/coinmonks/impersonating-accounts-with-hardhat-21212c94dcec