Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instructions to setup hardhat3 node #5987

Open
SebastienGllmt opened this issue Nov 27, 2024 · 0 comments
Open

Instructions to setup hardhat3 node #5987

SebastienGllmt opened this issue Nov 27, 2024 · 0 comments
Assignees

Comments

@SebastienGllmt
Copy link

SebastienGllmt commented Nov 27, 2024

hardhat3 node isn't available yet, so I created a guide on how to get it working

  1. Add @ignored/hardhat-vnext-viem to your package.json. Ideally this setup wouldn't depend on viem (since you might be using ethers), but this doesn't hurt and I need viem to query some of the state when starting the node
  2. Add viem to your plugins in hardhat.config.ts
import HardhatViem from "@ignored/hardhat-vnext-viem";

// you add the plugin to your existing configuration
const config: HardhatUserConfig = {
  plugins: [
    HardhatViem, // <- this is the line you want to add
  ],
}
  1. Add a devDependency on hardhat v2 (you won't actually use hardhat v2 at all, we just need it for one utility function): npm install --save-dev hardhat
  2. Define a task that creates the hardhat3 node command. To do that, copy the following block of code to you hardhat.config.ts (will explain more in below)
import { ArgumentType } from "@ignored/hardhat-vnext/types/arguments";
import { JsonRpcServer } from "hardhat/internal/hardhat-network/jsonrpc/server";
import type { NetworkConfig } from "@ignored/hardhat-vnext/types/config";
import { task } from "@ignored/hardhat-vnext/config";
import fs from "node:fs";

function getNetworkList(networks: Record<string, NetworkConfig>) {
  const networkEntries = Object.entries(networks);
  // ensure we always run the `hardhat` network first
  networkEntries.sort((a, b) => {
    if (a[0] === "hardhat") return -1;
    if (b[0] === "hardhat") return 1;
    return 0;
  });
  return networkEntries.filter(([name]) =>
    // skip the builtin localhost network, since hardhat node is meant to explicitly not use it
    name !== "localhost" &&
    // hardhat network seems broken and the block number never advances on it
    name !== "hardhat"
  );
}

const nodeTask = task("node")
  .addOption({
    name: "port",
    type: ArgumentType.INT,
    defaultValue: 8545,
  })
  .addOption({
    name: "hostname",
    type: ArgumentType.STRING,
    defaultValue: "127.0.0.1",
  })
  .setAction(
    async (args, hre): Promise<void> => {
      const hostname = (() => {
        if (args.hostname !== "127.0.0.1") {
          return args.hostname;
        }
        const insideDocker = fs.existsSync("/.dockerenv");
        return insideDocker ? "0.0.0.0" : "127.0.0.1";
      })();
      let port = args.port;

      const connections: JsonRpcServer[] = [];
      const networkEntries = getNetworkList(hre.config.networks);
      for (const [name, network] of networkEntries) {
        const connection = await hre.network.connect(name, network.chainType);

        const server = new JsonRpcServer({
          hostname,
          port,
          provider: connection.provider,
        });
        port++; // increase port so next network has a unique port number

        const publicClient = await connection.viem.getPublicClient();
        publicClient.watchBlocks(
          {
            onBlock: (block) => {
              // there seems to be a bug on block 0 where it triggers watchBlock in an infinite loop
              if (block.number === 0n) return;
              console.log(`${name}: block ${block.number} (${block.hash})`);
              if (block.transactions.length > 0) {
                console.log(
                  "Transactions: ",
                  block.transactions.map((tx) => tx.hash),
                );
              }
            },
            includeTransactions: true,
          },
        );
        const { port: actualPort, address } = await server.listen();
        console.log(
          `Started HTTP and WebSocket JSON-RPC server at ${address}:${actualPort}`,
        );
        console.log(); // new line
        connections.push(server);

        console.log("Accounts for ", name);
        console.log("========");
        // we use this over network.genesisAccounts
        // since we don't have access to some of the hardhat v3 util functions otherwise
        const wallets = await connection.viem.getWalletClients();
        for (let i = 0; i < wallets.length; i++) {
          const weiBalance = await publicClient.getBalance({
            address: wallets[i].account.address,
          });
          const balance = (weiBalance / 10n ** 18n).toString(10);
          console.log(
            `Account #${i}: ${wallets[i].account.address} (${balance} ETH)`,
          );
        }
        console.log(); // new line
      }
      await Promise.all(
        connections.map((connection) => connection.waitUntilClosed()),
      );
    },
  )
  .build();
  1. Add this new task to your hardhat.config.ts
import HardhatViem from "@ignored/hardhat-vnext-viem";

// you add the plugin to your existing configuration
const config: HardhatUserConfig = {
  tasks: [
    nodeTask, // <- this is the line you want to add
  ],
}
  1. Add some network to your configuration in hardhat.config.ts
import HardhatViem from "@ignored/hardhat-vnext-viem";

// you add the plugin to your existing configuration
const config: HardhatUserConfig = {
  networks: {
    edrHardhat: {
      type: "edr",
      chainType: "l1",
      chainId: 31337,
      automine: true,
      intervalMining: 2000,
    },
  },
}
  1. run npx hardhat3 node

It should now run!
image

If you want to add more networks, you can just add them to the networks like shown with edrHardhat

How to wait on the networks

In Hardhat v2, there was always only one network, so if you have a command you wanted to run after your local chain was setup, you could just wait on the 8545 port to start. However, since Hardhat v3 supports running multiple networks, waiting on all of them requires knowing how many networks are in your configuration and their port number

As a limited workaround for this (this just waits until the networks are started - it doesn't check if any contracts have been deployed to them so far), you can add a task that waits for all the networks to be started with the following steps:

  1. npm install --save-dev wait-on
  2. Add the following typescript code
import waitOn from "wait-on";

const nodeWaitTask = task(["node", "wait"])
  .addOption({
    name: "port",
    type: ArgumentType.INT,
    defaultValue: 8545,
  }).setAction(
    async (args, hre): Promise<void> => {
      const networkEntries = getNetworkList(hre.config.networks);
      for (
        let port = args.port;
        port < args.port + networkEntries.length;
        port++
      ) {
        await waitOn({
          resources: [`tcp:${port}`],
        });
        port++;
      }
    },
  )
  .build();

const config: HardhatUserConfig = {
  tasks: [
    nodeTask,
    nodeWaitTask, // <-- new entry here
  ],
}

Bugs and comments

  1. This guide depends on the viem plugin which is unfortunate, but I need some client to rebuild
    1. the log messages when new blocks are made
    2. logHardhatNetworkAccounts to list the accounts for the network
  2. This depends on hardhat v2 just for the JsonRpcServer because it's not exported in hardhat3
  3. I had to add an explicitly edrHardhat and disable the built-in hardhat network because the built-in hardhat network seems to permanently be stuck at block 0 and never moves
  4. I had to filter out block 0 for publicClient.watchBlocks otherwise the watch would trigger continuously in a loop. Not sure if this is a viem bug or a edr bug
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Backlog
Development

No branches or pull requests

2 participants