Lesson 16 of 21
In Progress

Setting Up a DApp Project with Hardhat

As a blockchain developer, you may find yourself working on decentralized applications (DApps) that run on Ethereum or other smart contract platforms. In this chapter, we’ll look at how to set up a DApp project using Hardhat, a development toolkit for Ethereum.

Why use Hardhat for DApp Development?

Hardhat is a powerful tool that simplifies the process of developing and testing DApps. Some of its key features include:

  • Easy-to-use command line interface (CLI) for running tasks such as deploying contracts, running tests, and debugging
  • Built-in test environment for running tests against a simulated blockchain
  • Integration with popular testing libraries such as Mocha and Chai
  • Built-in debugger for stepping through code and inspecting variables
  • Support for external libraries and contracts
  • Support for Solidity v0.6.x

Setting Up a Hardhat Project for DApp Development

To get started with Hardhat, you’ll need to install it and create a new project.

  1. Install Hardhat by running npm install -g hardhat
  2. Create a new directory for your project and navigate to it in your terminal
  3. Initialize a new Hardhat project by running hardhat init
  4. This will create a hardhat.config.js file in your project directory, which you can use to configure Hardhat.
// hardhat.config.js

module.exports = {
  // ...
};

Configuring your Hardhat Project

Next, you’ll need to configure your Hardhat project for DApp development.

  1. Add the @hardhat/contracts plugin to your project by running npm install @hardhat/contracts
  2. Enable the plugin in your hardhat.config.js file by adding the following code:
// hardhat.config.js

const { usePlugin } = require("hardhat/config");

usePlugin("@hardhat/contracts");

module.exports = {
  // ...
};

This plugin provides a set of tasks for working with contracts, such as compiling, testing, and deploying them.

Writing and Testing Solidity Contracts

Now that you have a Hardhat project set up, you can start writing and testing Solidity contracts.

  1. Create a contracts directory in your project and add a Solidity file, such as MyContract.sol.
  2. Write your Solidity contract in this file.
  3. To compile your contract, run hardhat compile
  4. To run tests against your contract, create a test directory and add a JavaScript test file, such as MyContract.test.js.
  5. Write your tests using a testing library such as Mocha and Chai.
  6. To run your tests, use the hardhat test command.

Here’s an example of a simple Solidity contract and a test for it:

pragma solidity ^0.6.0;

contract MyContract {
  uint256 public value;

  function setValue(uint256 _value) public {
    value = _value;
  }

  function getValue() public view returns (uint256) {
    return value;
  }
}
import { expect } from "chai";
import { Contract, ethers } from "hardhat";

describe("MyContract", () => {
  let contract: Contract;

  before(async () => {
    contract = await ethers.getContract("MyContract");
  });

  it("should have a value of 0 initially", async () => {
    expect(await contract.getValue()).to.equal("0");
  });

  it("should set the value correctly", async () => {
    await contract.setValue(42);
    expect(await contract.getValue()).to.equal("42");
  });
});

Deploying Contracts with Hardhat

Once you’ve written and tested your contracts, you’ll need to deploy them to the blockchain in order to use them in your DApp. Hardhat makes this process easy with its built-in deployment tasks.

  1. To deploy a contract, create a migrations directory and add a JavaScript migration file, such as 1_deploy_contracts.js.
  2. Write your migration using the deploy function provided by Hardhat’s @hardhat/contracts plugin.
  3. Run the migration using the hardhat run command, followed by the name of the migration file.

Here’s an example of a migration that deploys the MyContract contract:

const { deploy } = require("hardhat/deploy");

async function main() {
  const contract = await deploy("MyContract");
  console.log("Contract deployed to:", contract.address);
}

main();

Interacting with Deployed Contracts

Once your contracts are deployed, you can interact with them using Hardhat’s built-in contract abstraction.

  1. To call a view function of a contract, use the call method.
  2. To call a non-view function of a contract, use the sendTransaction method.
  3. To pass arguments to a function, pass them as additional arguments to the call or sendTransaction method.

Here’s an example of calling a function of a deployed contract:

import { ethers } from "hardhat";

async function main() {
  const contract = await ethers.getContract("MyContract", "0x123...");
  const value = await contract.call("getValue");
  console.log("Value:", value.toString());
}

main();

Migrations and Deployment Management

In a real-world DApp project, you’ll often need to deploy multiple contracts and manage their dependencies. Hardhat provides a number of features to make this process easier.

  1. To deploy contracts in a specific order, use the then method of the deployer object.
  2. To deploy dependencies of a contract, use the link method.
  3. To deploy and test contracts on multiple networks, use the network option in your hardhat.config.js file.

Here’s an example of deploying a contract and its dependencies using migrations:

const { deploy, link } = require("hardhat/deploy");

async function main() {
  const dependency = await deploy("Dependency");
  console.log("Dependency deployed to:", dependency.address);

  const contract = await deploy("MyContract", {
    dependencies: [dependency],
  });
  console.log("Contract deployed to:", contract.address);
}

main();

You can also use the link method to link a deployed contract to another contract in your project:

const { link } = require("hardhat/deploy");

async function main() {
  const dependency = await link("Dependency", "0x123...");
  console.log("Dependency linked:", dependency.address);
}

main();

Advanced Testing and Debugging Techniques

Hardhat provides a number of advanced testing and debugging techniques to help you develop and debug your DApp.

  1. To test your DApp against different networks, use the network option in your hardhat.config.js file. This allows you to specify different network configurations for different environments, such as development, staging, and production.
module.exports = {
  networks: {
    development: {
      url: "http://localhost:8545",
      accounts: [
        {
          secretKey: "0x123...",
          balance: "10000000000000000000000",
        },
      ],
    },
    staging: {
      url: "https://staging-rpc.example.com",
      accounts: [
        {
          secretKey: "0x456...",
          balance: "10000000000000000000000",
        },
      ],
    },
  },
};
  1. To debug your Solidity contracts, use the debug command in the Hardhat CLI. This allows you to step through the execution of a contract and inspect the state of variables and memory at each step.
hardhat debug MyContract.sol:MyContract
  1. To debug your JavaScript code, use a debugger such as the one provided by your code editor. This allows you to set breakpoints and inspect the state of variables at different points in your code.
  2. To inspect the state of a contract during a test, use the snapshot and revert functions provided by Hardhat’s @hardhat/test-environment plugin. These functions allow you to take a snapshot of the contract’s state at a particular point in a test, and then revert back to that state at any point later in the test. This is useful for testing scenarios where the contract’s state changes multiple times.
import { expect } from "chai";
import { Contract, ethers, providers } from "hardhat";
import { snapshot, revert } from "@hardhat/test-environment";

describe("MyContract", () => {
  let contract: Contract;

  before(async () => {
    contract = await ethers.getContract("MyContract");
  });

  it("should change the value correctly", async () => {
    // Take a snapshot of the contract's state
    const snapshotId = await snapshot();

    // Change the value of the contract
    await contract.setValue(42);
    expect(await contract.getValue()).to.equal("42");

    // Revert to the snapshot
    await revert(snapshotId);

    // Check that the value has been reset
    expect(await contract.getValue()).to.equal("0");
  });
});

Setting Up a DApp Project with Hardhat

Now that you have learned the basics of using Hardhat for blockchain development, you are ready to set up a full DApp project. Here are some tips to get you started:

  1. Start by creating a new Hardhat project using the hardhat new command. This will generate a basic project structure with a contracts directory for your Solidity contracts, a test directory for your tests, and a hardhat.config.js file for configuration.
  2. Write and test your Solidity contracts using the techniques you learned in previous chapters. Use the hardhat compile and hardhat test commands to compile and test your contracts.
  3. Set up your DApp frontend using a framework such as React or Angular. You can use the hardhat run command to start a local development server for your frontend.
  4. Use the hardhat deploy command to deploy your contracts to the desired network. You can use the network option in your hardhat.config.js file to specify different deployment configurations for different networks.
  5. Use the @hardhat/web3 plugin to interact with your deployed contracts from your DApp frontend. You can use the getContract function to get an instance of a contract, and then call its functions as you would in any other JavaScript code.
  6. Use the @hardhat/hardhat-ethers-v5 plugin to sign transactions and create contracts programmatically. This is useful for scenarios where you want to create contracts on-the-fly, or sign transactions on behalf of the user.
  7. Use the @hardhat/network plugin to manage your deployments and interact with the Ethereum network. This plugin provides a number of useful functions for tasks such as deploying contracts, sending transactions, and querying the blockchain.

Conclusion

In this article, you learned how to set up a DApp project with Hardhat. You learned how to use the hardhat new command to create a new project, and how to use the hardhat compile and hardhat test commands to compile and test your contracts. You also learned how to deploy your contracts to the Ethereum network using the hardhat deploy command, and how to interact with them from your DApp frontend using the @hardhat/web3 plugin. Finally, you learned how to use the @hardhat/hardhat-ethers-v5 and @hardhat/network plugins to sign transactions and manage your deployments.

With these tools and techniques, you should be well-equipped to develop and deploy your own DApps using Hardhat. Happy coding!

Exercises

To review these concepts, we will go through a series of exercises designed to test your understanding and apply what you have learned.

Create a new Hardhat project using the hardhat new command.

To create a new Hardhat project, navigate to the desired directory in your terminal and enter the following command:

hardhat new my-project

This will generate a new project with the following structure:

my-project/
├── contracts/
├── test/
└── hardhat.config.js

Write a Solidity contract that has a public variable and a public function. Compile and test the contract using the hardhat compile and hardhat test commands.

To write a Solidity contract with a public variable and a public function, you can use the following code:

pragma solidity ^0.6.0;

contract MyContract {
    uint public myVariable;
    function setMyVariable(uint _value) public {
        myVariable = _value;
    }
}

To compile this contract, navigate to your Hardhat project directory and enter the following command:

hardhat compile

This will compile your contract and generate a JSON artifact file in the build/contracts directory.

To test this contract, you can write a test using the Hardhat testing framework. For example:

const { expect } = require("hardhat");
const { ethers } = require("hardhat");
const MyContract = require("../build/MyContract.json");

describe("MyContract", function () {
  it("should set myVariable correctly", async function () {
    const contract = await ethers.getContractAt(MyContract.abi, MyContract.address);
    await contract.setMyVariable(42);
    expect(await contract.myVariable()).to.equal(42);
  });
});

To run this test, enter the following command:

hardhat test

This will run your test and print the results to the terminal.

Deploy your contract to the Ethereum network using the hardhat deploy command.

To deploy your contract to the Ethereum network, you will need to configure your hardhat.config.js file with the desired network settings. For example:

const { join } = require("path");

module.exports = {
  solidity: "0.6.12",
  networks: {
    rinkeby: {
      url: "https://rinkeby.infura.io/v3/YOUR-INFURA-API-KEY",
      accounts: [
        {
          secretKey: "YOUR-PRIVATE-KEY",
          balance: "10 ether",
        },
      ],
    },
  },
  paths: {
    sources: join(__dirname, "contracts"),
  },
};

Once you have configured your network settings, you can deploy your contract using the following command:

hardhat deploy --network rinkeby

This will deploy your contract to the Rinkeby test network.

Use the @hardhat/hardhat-ethers-v5 plugin to sign a transaction and create a contract programmatically.

To use the @hardhat/hardhat-ethers-v5 plugin to sign a transaction and create a contract programmatically, you can use the following code:

const { ethers } = require("hardhat");
const MyContract = require("../build/MyContract.json");

async function main() {
  // Load the account
  const accounts = await ethers.getSigners();
  const account = accounts[0];

  // Sign a transaction
  const tx = await account.sendTransaction({
    to: "0x123...",
    value: ethers.utils.parseEther("1"),
  });
  console.log(`Transaction hash: ${tx.hash}`);

  // Deploy a contract
  const factory = new ethers.ContractFactory(MyContract.abi, MyContract.bytecode, account);
  const contract = await factory.deploy();
  console.log(`Contract address: ${contract.address}`);
}

main()
  .then(() => console.log("Done"))
  .catch((error) => console.error(error));

This code will sign a transaction and send 1 ETH to the specified address, and then deploy a new instance of the MyContract contract using the ContractFactory class. The ContractFactory class is provided by the @hardhat/hardhat-ethers-v5 plugin, and allows you to create contract instances programmatically using the contract ABI and bytecode.

Write a Solidity contract that emits an event when the setValue function is called. The event should have a single argument newValue of type uint256.

pragma solidity ^0.6.0;

contract MyContract {
  uint256 public value;

  event ValueChanged(uint256 newValue);

  function setValue(uint256 newValue) public {
    value = newValue;
    emit ValueChanged(newValue);
  }
}