Running a local Ethereum test chain - solving the testnet problem!

Running a local Ethereum test chain - solving the testnet problem!

We all have been to the same place- we write a smart contract to deploy on one of the Ethereum testnets, but you don't have enough funds to deploy this contract. We then use all sorts of testnets and find their faucets to get as many funds as possible to test our contracts. After countless efforts and Twitter verifications, you get very little funds you can barely even use to test.

In this case, we will talk about problems while developing contracts on EVM, and finally, we will look at how you can spin up your local node to fix all these problems.

The problems with developing on EVM

The sole purpose of testnets is to help developers test their contracts on a network before they move to the mainnet, where funds really matter. Ideally, getting these funds should be easy- provisioning funds on-demand should be the top priority of these testnets, although it's a complete nightmare.

Instead of focusing on developing their contract, developers are wasting their time getting test funds (which is supposed to be the easiest part). Ethereum recommends Goerli as a testnet to use, although funds on Goerli are so rare that a market has formed around it where people now buy Goerli ETH (yeah, imagine buying testnet funds to test your contracts).

I love how Solana devnet handles this; no faucets or anything, you use a CLI command, and you have funds in your wallet instantly each time you run the command. I think testnets should learn something from this and develop some solutions.

Creating a local Ethereum chain

This section will create our own local Ethereum chain for testing. We will see two ways of doing this- creating a chain from scratch and forking an existing chain and using it as a testnet (so that you have access to all the existing states of the chain, including already-deployed contracts).

We will use Hardhat to spin up a local node for both methods. Let's set up a hardhat project. Run the following command in your terminal to initialize a hardhat project:

npx hardhat

Now you should be presented with a wizard to create your hardhat project. Use any configuration you wish to (although I don't recommend using the empty project option because it has caused problems before). Normally, I choose a JavaScript project.

Configuring hardhat.config.js file

We need to configure the hardhat.config.js file hardhat created for us to have some details about our test chain. Have the following content in your hardhat.config.js file:

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: {
    version: "0.8.9",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },

  networks: {
    hardhat: {
      chainId: 1337,
    },
  },
};

In the above config, we are adding a new hardhat object under the networks object. Remember that the name must be hardhat as hardhat recognizes this as a configuration object while we run our node. We have set the chainId to be 1337 which is usually used for local testnet networks.

Now, you can use the following command to spin up your local node:

npx hardhat node --network hardhat

This will start your local node, and you should see something like this in your terminal:

Now, you can add a network in your wallet with chain ID 1337 and RPC URL as provided in your terminal. You can also use any default private keys to deploy contracts to your local chain or add them to MetaMask for testing!

NOTE: Never use any of the private keys provided by hardhat CLI in production environments. These are publicly known private keys and are only meant for testing! Bots are regularly monitoring these wallets on mainnets and any funds sent to these wallets WILL BE LOST! Always separate test and production wallets.

Funding your wallet for testing

If you read the warning above, you might want to use your test wallets for testing. However, funds are unavailable in your wallet by default on your local node. Now, we will write a script that gives you any number of local ETH in your wallet for testing.

Start by running the following command to install a package:

npm install @nomicfoundation/hardhat-network-helpers

After the installation is complete, create a new file called init.js inside scripts folder and have the following contents:

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

async function init() {
  const address = "your-wallet-address";

  await helpers.setBalance(address, 1000 * 1e18);
}

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

Make sure you replace the value of address variable with the wallet address you want to fund. You can also update the amount as you desire. Now, we need to add some more configuration to the hardhat.config.js file. Here's the updated file:

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: {
    version: "0.8.9",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },

  defaultNetwork: "running",

  networks: {
    hardhat: {
      chainId: 1337,
    },

    running: {
      url: "http://localhost:8545",
      chainId: 1337,
    },
  },
};

In the above configuration, we add another network called as running and point it to the local node running and use the chain ID the same as the hardhat network, i.e. 1337. We do this to interact with our running hardhat node using scripts. We also set the defaultNetwork to running so that we don't need to provide a network name each time we run a script.

Run the following command in your terminal to trigger the addition of funds to your wallet:

npx hardhat run ./scripts/init.js

After the command is run, you should see your updated funds in your wallet. If it doesn't show up in MetaMask, try switching networks back and forth.

While trying local networks on MetaMask, sometimes the transactions outright refuse to go through. This is usually because the network was restarted and the nonce was reset on the network side but is the same on MetaMask.

To fix this, go to MetaMask settings -> Advanced -> Reset Wallet (this will not wipe your wallets, it will only reset nonce in the wallet and remove transaction history).

Forking an existing network

If you want access to contracts deployed on some other chain while testing (such as the USDC token contract or any other contract), forking would be an easy way to achieve that. You can fork any network you wish to; all you need is the RPC URL.

You can get a FREE RPC URL without signing-up or connecting your wallet at thirdweb chainlist!

hardhat: {
  chainId: 1337,
  forking: {
    url: "RPC_URL",
    enabled: true,
  },
},

Make sure to replace RPC_URL with the appropriate RPC URL of the network, you wish to fork. Now, you can start the forked node with the following command:

npx hardhat node --network hardhat

This will have the same output as before, and you could add more test ETH to your wallet using the same script. However, now you will have access to all the existing deployed contracts, and all the wallets on the network will maintain balances.

Conclusion

In this article, you learnt how to spin up your local node and start developing your contracts! It's better to use a local node, unlike testnets, most of the time.

If you have better ideas on tackling the testnet situation, let me know in the comments below! Make sure to leave any feedback (if any) in the comments as well!