Hardhat Smart Contract Development

Prerequisites

  • MetaMask installed & connected to a local Creditcoin node or public testnet

  • A funded account

  • Node.js & NPM installed

Setting up Hardhat

To create a Hardhat project you must initialize a node project using npm. Navigate to the folder where you want to build your project and run:

npx hardhat init

If you don’t have Hardhat installed globally, you’ll be prompted to do so.

Follow the prompts to set up your project. You can choose options like creating a sample project or starting an empty project. For this tutorial, we will crate a TypeScript project.

Writing your first contract

Inside the contracts directory of your Hardhat project, create a new Solidity file (e.g., MyContract.sol).

For simplicity, we will first delete the example contract Hardhat created for us. Remove the Lock.sol file from the contracts folder, its associated test Lock.ts from the test folder and deploy.ts in the scripts directory. We will write their replacements.

For this tutorial we will use a simple Counter contract. You can copy and paste the following code into the contract file you have just created.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

contract Counter {
    int private count = 0;
    function incrementCounter() public {
        count += 1;
    }
    function decrementCounter() public {
        count -= 1;
    }

    function getCount() public view returns (int) {
        return count;
    }
}

Compiling

Before you can deploy or test your contract. You need to compile it. Run npx hardhat compile to do so.

Testing

Before we deploy the contract, we want to make sure it works as expected. We can use Hardhat to write and run some tests for our Counter contract.

Create a file inside the test folder called Counter.test.ts and add the following basic test.

// Counter.ts
import { expect } from 'chai';
import { ethers } from 'hardhat';

describe('Counter contract', function () {
    it("Should be deployed with the initial value 0", async function ()
    {
        const counter = await ethers.deployContract("Counter");
        await counter.deployed();
        const count = await counter.getCount();
        expect(count).to.equal(0);
    });
});

Test the contract by running npx hardhat test. You should see the following output.

$ npx hardhat test
  Counter contract
    ✔ Should be deployed with the initial value 0 (814ms)


  1 passing (824ms)

To add more tests to the contract, add more it statements to the test file.

// Counter.ts
import { expect } from 'chai';
import { ethers } from 'hardhat';
import { Contract } from 'ethers';

describe('Counter contract', function () {
    let counter: Contract;

    before(async function () {
        // Deploy the Counter contract before the tests
        const CounterFactory = await ethers.getContractFactory("Counter");
        counter = await CounterFactory.deploy();
        await counter.deployed();
    });

    // Example of a test...  
    it('Should increment count by 1', async function () {
        await counter.incrementCounter();
        expect(await counter.getCount()).to.equal(1);
    });
});

Deploying a smart contract

To deploy your contract, you will need to write a deploy script. Create a file named deploy.ts

import { ethers } from "hardhat";

async function main() {
  // Get the ContractFactory and Signers
  const [deployer] = await ethers.getSigners();
  const Counter = await ethers.getContractFactory("Counter", deployer);

  // Deploy the contract
  const counter = await Counter.deploy();
  await counter.deployed();

  console.log(`Counter contract deployed to: ${counter.address}`);
}

// Hardhat recommends this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
});

You can deploy the contract by running:

npx hardhat run scripts/deploy.js --network <network-name>

Without the --network flag, the deployment will run against the local Hardhat Network, which means it gets lost after the command finishes running. But it is useful for testing that our deployment script works.

$ npx hardhat run scripts/deploy.js
Counter contract address: 0x5FbDB2315678afecb367f032d93F642f64180aa3

To deploy the contract to a remote network, we will need to add a network to hardhat.config.ts. In this example we will use Creditcoin Testnet.

Avoid at all costs hard-coding credentials into source code. Use the vars.get method in combination with the vars set Hardhat command to set them up in a safe way. You can also use environment variables prefixed with HARDHAT_VAR_ to override the values of configuration variables.

HARDHAT_VAR_CC3TEST_PRIVATE_KEY=123

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

// Replace this private key with your
// Creditcoin Testnet account private key
// To export your private key from MetaMask, open MetaMask and
// go to Account Details > Export Private Key
// Beware: NEVER put real CTC into testing accounts
const CC3TEST_PRIVATE_KEY = vars.get("CC3TEST_PRIVATE_KEY");

const config: HardhatUserConfig = {
  solidity: "0.8.19", // Replace by the solidity version you want to deploy
  networks: {
    creditcoin_testnet: {
      url: "https://rpc.cc3-testnet.creditcoin.network",
      accounts: [CC3TEST_PRIVATE_KEY]
    }
  }
};

export default config;

You will then need to set the CC3 development private key by running the Hardhat vars set command:

$ npx hardhat vars set CC3TEST_PRIVATE_KEY
✔ Enter value: ********************************

After setting up the new config, run:

npx hardhat run scripts/deploy.ts --network creditcoin_testnet

Forking CC3 EVM

If you wish to test using the current state of a network, you can use Hardhat to fork a Creditcoin network and test locally against it.

npx hardhat node --fork <creditcoin-endpoint>

This will spin up a local node using the state from the forked network. It comes with all its deployed contracts, current balances and new funded accounts.

Last updated