Hardhat is a new smart contract framework that is gaining popularity in the past year. It makes development and debugging easier with minimual setup or configuration. In this post we will review how to get started with Hardhat framework
You may also checkout the video tutorial from our YouTube Channel:
Overview
Hardhat was created by Nomic Labs, helps developers to manage and automate the common tasks of the process of building smart contracts and Dapps, as easily introducing more functionality around this workflow. That means you can compile, run and test smart contracts at the very core. Hardhat is gaining populartiy according to the last solidity developer survey 2020.
Hardhat comes built-in with Hardhat Network, a local Ethereum network designed for development. Its functionality focuses around Solidity debugging, featuring stack traces, console.log()
and explicit error messages when transactions fail.
Hardhat Features
- Easily Run Solidity Locally: Easily deploy your contracts, run tests and debug Solidity code without dealing with live environments. Hardhat Network is a local Ethereum network designed for development.
- Built-in debugging flow: Hardhat is the best choice for Solidity debugging. You get Solidity stack traces, console.log and explicit error messages when transactions fail.
- Large Plugin Ecosystem: Extend Hardhat with a composable ecosystem of plugins that add functionality and integrate your existing tools into a smooth workflow.
- Built-in typescript support and test suite: Catch mistakes before you even run your code by switching to a typed language. Hardhat provides full native support for TypeScript.
Installing Hardhat
To get started create a new folder and run: npm init -y
Once your project is ready, you should run npm install --save-dev hardhat
to use your local installation of Hardhat, you need to use npx
to run it (i.e. npx hardhat
). Lets create a Hardhat project, run npx hardhat
in your project folder.
Once the project is setup, your folder structure should look something like this:
These are the default paths for a Hardhat project: – contracts/
is where the source files for your contracts should be. – test/
is where your tests should go. – scripts/
is where simple automation scripts go.
If you need to change these paths, take a look at the paths configuration section.
Hardhat Network is initialized by default in this state:
- A brand new blockchain, just with the genesis block.
- 20 accounts with 10000 ETH each, generated with the mnemonic
"test test test test test test test test test test test junk"
. Their addresses are:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
0x70997970C51812dc3A010C7d01b50e0d17dc79C8
0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
0x90F79bf6EB2c4f870365E785982E1f101E93b906
0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65
0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc
0x976EA74026E726554dB657fA54763abd0C3a0aa9
0x14dC79964da2C08b23698B3D3cc7Ca32193d9955
0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f
0xa0Ee7A142d267C1f36714E4a8F75612F20a79720
0xBcd4042DE499D14e55001CcbB24a551F3b954096
0x71bE63f3384f5fb98995898A86B02Fb2426c5788
0xFABB0ac9d68B0B445fB7357272Ff202C5651694a
0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec
0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097
0xcd3B766CCDd6AE721141F452C550Ca635964ce71
0x2546BcD3c84621e976D8185a91A922aE77ECEc30
0xbDA5747bFD65F08deb54cb465eB87D40e51B197E
0xdD2FD4581271e230360230F9337D5c0430Bf44C0
0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199
To customise it, take a look at the configuration section.
A Simple Solidity Smart Contract
The default project ships with a simple smart contract:
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "hardhat/console.sol";
contract Greeter {
string private greeting;
constructor(string memory _greeting) {
console.log("Deploying a Greeter with greeting:", _greeting);
greeting = _greeting;
}
function greet() public view returns (string memory) {
return greeting;
}
function setGreeting(string memory _greeting) public {
console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
greeting = _greeting;
}
}
The contract essentially sets a private variable when you call the setGreeting
function.
Compiling Your Contract
Run the following command to compile your contract:
npx hardhat compile
Interacting and Testing your smart contracts
One the easiest way to interact and check your smart contract functionality is to write unit tests for it and see if the contract is behaving as expected. Writing smart contract tests in Hardhat is done using JavaScript or TypeScript.
Tests using Waffle are written with Mocha (opens new window)alongside Chai (opens new window). If you haven’t heard of them, they are super popular JavaScript testing utilities.
Inside the test
folder you’ll find sample-test.js
. Let’s take a look at it, and we’ll explain it next:
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Greeter", function () {
it("Should return the new greeting once it's changed", async function () {
const Greeter = await ethers.getContractFactory("Greeter");
const greeter = await Greeter.deploy("Hello, world!");
await greeter.deployed();
expect(await greeter.greet()).to.equal("Hello, world!");
const setGreetingTx = await greeter.setGreeting("Hola, mundo!");
// wait until the transaction is mined
await setGreetingTx.wait();
expect(await greeter.greet()).to.equal("Hola, mundo!");
});
});
In your terminal, run npx hardhat test
. You should see the following output:
You can also use web3.js instead of ehter.js your tests, an instance of it is available in the global scope. You can see this in the describe()
test in Greeter.js
:
const Greeter = artifacts.require("Greeter");
// Traditional Truffle test
contract("Greeter", accounts => {
it("Should return the new greeting once it's changed", async function() {
const greeter = await Greeter.new("Hello, world!");
assert.equal(await greeter.greet(), "Hello, world!");
await greeter.setGreeting("Hola, mundo!");
assert.equal(await greeter.greet(), "Hola, mundo!");
});
});
// Vanilla Mocha test. Increased compatibility with tools that integrate Mocha.
describe("Greeter contract", function() {
let accounts;
before(async function() {
accounts = await web3.eth.getAccounts();
});
describe("Deployment", function() {
it("Should deploy with the right greeting", async function() {
const greeter = await Greeter.new("Hello, world!");
assert.equal(await greeter.greet(), "Hello, world!");
const greeter2 = await Greeter.new("Hola, mundo!");
assert.equal(await greeter2.greet(), "Hola, mundo!");
});
});
});
Checkout the plugin’s [README file ] (https://github.com/nomiclabs/hardhat/tree/master/packages/hardhat-truffle5)for more information about it.
Working with local hardhat node
Start a hardhat node
bash
npx hardhat node
Connect hardhat node to Metamask
Open Metamask > Select the network dropdown from the top left > Select Custom RPC
and enter the following details:
- Network Name:
<Enter a name for the network>
- New RPC URL:
http://127.0.0.1:8545
-
Chain ID:
31337
Click save. You can use this network to connect to the local hardhat node.
Connect your local hardhat account to Metamask for making transactions
- After running
npx hardhat node
you will see a list of 20 addresses logged in the terminal - To configure an account copy its private key from the terminal (i.e the text after
Private Key:
) - Open Metamask > Click the account icon on top right > Import Account > Paste the private key you just copied > click Import
- You should now have the account connected with 10000 ETH
Deploying your contracts
When it comes to deploying, there are no official plugins that implement a deployment system for Hardhat yet, but there’s an open issuewith some ideas and we’d value your opinion on how to best design it.
In the meantime, we recommend deploying your smart contracts using scripts, or using the hardhat-deploy community plugin. You can deploy the Greeter
contract from the sample project with a deploy script scripts/deploy.js
like this:
async function main() {
// We get the contract to deploy
const Greeter = await ethers.getContractFactory("Greeter");
const greeter = await Greeter.deploy("Hello, Hardhat!");
console.log("Greeter deployed to:", greeter.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
You can deploy in the localhost
network following these steps:
- Start a local node
npx hardhat node
- Open a new terminal and deploy the smart contract in the
localhost
network
npx hardhat run --network localhost scripts/deploy.js
As general rule, you can target any network configured in the hardhat.config.js
npx hardhat run --network <your-network> scripts/deploy.js
Using Ganache instead of the Hardhat Network
You don’t need to do anything special to use Ganache if you don’t want to.
Just start Ganache and then run Hardhat with
npx hardhat --network localhost test
Using the hardhat-ganache
plugin
If you don’t want to manually start and stop Ganache every time, you can use the hardhat-ganache
plugin.
This plugin creates a network called ganache
, and automatically starts and stops Ganache before and after running your tests and scripts.
To use it, you have to install it with npm
npm install --save-dev @nomiclabs/hardhat-ganache
and add this line at the beginning of your hardhat.config.js
require("@nomiclabs/hardhat-ganache");
Finally, you can run your tests with
npx hardhat --network ganache test
Writing your custom scripts
Inside scripts/
you will find sample-script.js
. Read through its comments to have a better idea of what it does.
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// When running the script with `npx hardhat run <script>` you'll find the Hardhat
// Runtime Environment's members available in the global scope.
const hre = require("hardhat");
async function main() {
// Hardhat always runs the compile task when running scripts with its command
// line interface.
//
// If this script is run directly using `node` you may want to call compile
// manually to make sure everything is compiled
// await hre.run('compile');
// We get the contract to deploy
const Greeter = await hre.ethers.getContractFactory("Greeter");
const greeter = await Greeter.deploy("Hello, Hardhat!");
await greeter.deployed();
console.log("Greeter deployed to:", greeter.address);
}
// We recommend 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);
});
Before running the script with node
you need to declare ethers
. This is needed because Hardhat won’t be injecting it on the global scope as it does when calling the run
task.
const hre = require("hardhat");
const ethers = hre.ethers;
async function main() {
//...
}
To run the script, execute
node scripts/sample-script.js
By accessing the Hardhat Runtime Environment at the top, you are allowed to run the script in a standalone fashion. Hardhat always runs the compile task when it’s invoked via npx hardhat run
, but in a standalone fashion you may want to call compile manually to make sure everything is compiled.
Conclusion
Hardhat is a powerful framework to compile, deploy, test, and debug your Ethereum software. It helps developers manage and automate the recurring tasks that are inherent to the process of building smart contracts and dApps, as well as easily introducing more functionality around this workflow. This means compiling, running and testing smart contracts at the very core. It is gaining popularity in the recent months and its definitely a tool that you should get familiar with to ease local development.
Leave a Reply