Hardhat

Hardhat

Hardhat is a development environment to compile, deploy, test, and debug your Ethereum software. It helps developers manage and automate the recurring tasks inherent to the process of building smart contracts and dApps and easily introduces more functionality around this workflow. This means compiling, running and testing smart contracts at the very core.

Note: This page is just a quick start based on a sample program; please refer to Hardhat's documentation for more information on Hardhat installation.

Installing Node.js

To use Hardhat, use the Nodejs package manager npm. Hardhat is installed on a per-project basis, so at this point, the environment building is complete if you can execute the npm command in a terminal on your PC.

Creating a Hardhat Project

Create a project named hardhat-tutorial for npm and install hardhat.

mkdir hardhat-tutorial
cd hardhat-tutorial
npm init --yes
npm install --save-dev hardhat

Now hardhat is installed in the new project, run the hardhat command to create the hardhat configuration file.

npx hardhat init

When presented with a choice, select Create an empty hardhat.config.js.

Plugins

Hardhat is unopinionated in terms of what tools you end up using, but it does come with some built-in defaults. All of which can be overridden. Most of the time the way to use a given tool is by consuming a plugin that integrates it into Hardhat.

In this tutorial we are going to use our recommended plugin, @nomicfoundation/hardhat-toolbox, which has everything you need for developing smart contracts.

To install it, run this in your project directory:

npm install --save-dev @nomicfoundation/hardhat-toolbox

Add the highlighted line to your hardhat.config.js so that it looks like this:



/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.24",
};

Writing and compiling smart contracts

We're going to create a simple smart contract that implements a token that can be transferred. Token contracts are most frequently used to exchange or store value. We won't go in depth into the Solidity code of the contract on this tutorial, but there's some logic we implemented that you should know:

  • There is a fixed total supply of tokens that can't be changed.

  • The entire supply is assigned to the address that deploys the contract.

  • Anyone can receive tokens.

  • Anyone with at least one token can transfer tokens.

  • The token is non-divisible. You can transfer 1, 2, 3 or 37 tokens but not 2.5.

Writing smart contracts

Start by creating a new directory called contracts and create a file inside the directory called Token.sol.

//SPDX-License-Identifier: UNLICENSED

// Solidity files have to start with this pragma.
// It will be used by the Solidity compiler to validate its version.
pragma solidity ^0.8.0;


// This is the main building block for smart contracts.
contract Token {
    // Some string type variables to identify the token.
    string public name = "CVC Hardhat Token";
    string public symbol = "CHT";

    // The fixed amount of tokens, stored in an unsigned integer type variable.
    uint256 public totalSupply = 1000000;

    // An address type variable is used to store ethereum accounts.
    address public owner;

    // A mapping is a key/value map. Here we store each account's balance.
    mapping(address => uint256) balances;

    // The Transfer event helps off-chain applications understand
    // what happens within your contract.
    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    /**
     * Contract initialization.
     */
    constructor() {
        // The totalSupply is assigned to the transaction sender, which is the
        // account that is deploying the contract.
        balances[msg.sender] = totalSupply;
        owner = msg.sender;
    }

    /**
     * A function to transfer tokens.
     *
     * The `external` modifier makes a function *only* callable from *outside*
     * the contract.
     */
    function transfer(address to, uint256 amount) external {
        // Check if the transaction sender has enough tokens.
        // If `require`'s first argument evaluates to `false` then the
        // transaction will revert.
        require(balances[msg.sender] >= amount, "Not enough tokens");

        // Transfer the amount.
        balances[msg.sender] -= amount;
        balances[to] += amount;

        // Notify off-chain applications of the transfer.
        emit Transfer(msg.sender, to, amount);
    }

    /**
     * Read only function to retrieve the token balance of a given account.
     *
     * The `view` modifier indicates that it doesn't modify the contract's
     * state, which allows us to call it without executing a transaction.
     */
    function balanceOf(address account) external view returns (uint256) {
        return balances[account];
    }
}

Compiling contracts

To compile the contract run npx hardhat compile in your terminal. The compile task is one of the built-in tasks.

npx hardhat compile

You should see the following output:

> hardhat-tutorial@1.0.0 npx

> hardhat compile

Compiled 1 Solidity file successfully (evm target: paris).

The contract has been successfully compiled and it's ready to be used.

Testing contracts

To test our contract, we are going to use Hardhat Network, a local Ethereum network designed for development. It comes built-in with Hardhat, and it's used as the default network. You don't need to setup anything to use it.

In our tests we're going to use ethers.js to interact with the Ethereum contract we built in the previous section, and we'll use Mocha as our test runner.

Writing tests

Create a new directory called test inside our project root directory and create a new file in there called Token.js.

Let's start with the code below. We'll explain it next, but for now paste this into Token.js:

const { expect } = require("chai");

describe("Token contract", function () {
  it("Deployment should assign the total supply of tokens to the owner", async function () {
    const [owner] = await ethers.getSigners();

    const hardhatToken = await ethers.deployContract("Token");

    const ownerBalance = await hardhatToken.balanceOf(owner.address);
    expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
  });
});

Run tests against the compiled smart contract using the test code above.

npx hardhat test

You should see the following output:

> hardhat-tutorial@1.0.0 npx

> hardhat test

Token contract

✔ Deployment should assign the total supply of tokens to the owner (409ms)

1 passing (410ms)

Deploying to Kura Testnet

Setting Environment Variables

Set the following environment variables

  • KURA_PRIVATE_KEY

  • KURA_RPC_URL

env.txt

Credentials such as private keys and API keys should not be committed to Git. So, you can put the contents of your environment variables in a file called env.txt and put them outside of your Git project, or you can use .gitignore to avoid committing them to Git.

export KURA_PRIVATE_KEY="<EOA private key>"
export KURA_RPC_URL="https://rpc-kura.cross.technology/"

Executing the following commands in a terminal window with environment variables set will cause the values of the environment variables to be read.

source env.txt

hardhat.config.js

Rewrite hardhat.config.js as follows

require("@nomicfoundation/hardhat-toolbox");

const KURA_PRIVATE_KEY = process.env.KURA_PRIVATE_KEY;
const KURA_RPC_URL = process.env.KURA_RPC_URL;

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.24",
  networks: {
    kura: {
      url: `${KURA_RPC_URL}`,
      accounts: [`${KURA_PRIVATE_KEY}`],
    },
  },
};

Deploying

At the software level, deploying to a testnet is the same as deploying to mainnet. The only difference is which network you connect to. Let's look into what the code to deploy your contracts using Hardhat Ignition would look like.

In Hardhat Ignition, deployments are defined through Ignition Modules. These modules are abstractions to describe a deployment; that is, JavaScript functions that specify what you want to deploy.

Ignition modules are expected to be within the ./ignition/modules directory. Let's create a new directory ignitioninside the project root's directory, then, create a directory named modules inside of the ignition directory. Paste the following into a Token.js file in that directory:

const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");

const TokenModule = buildModule("TokenModule", (m) => {
  const token = m.contract("Token");

  return { token };
});

module.exports = TokenModule;

To tell Hardhat to connect to Kura Testnet, you can use the --networkparameter when running any task, like this:

npx hardhat ignition deploy ./ignition/modules/Token.js --network kura

If you see a log like this, the deployment to the test net has been successfully executed.

Check the CROSSVALUE SCAN (Kura Testnet) for contract addresses and transactions.

Check with Metamask

Register the Token address output when deploying to Metamask.

Last updated