Let’s create the JavaScript or TypeScript project and go through these steps to compile, test and deploy the sample contract. We recommend using TypeScript, but if you are not familiar with it just pick JavaScript.
3. Running tasks
To first get a quick sense of what's available and what's going on, run npx hardhat in your project folder:
$ npx hardhat
Hardhat version 2.9.9
Usage: hardhat [GLOBAL OPTIONS] <TASK> [TASK OPTIONS]
GLOBAL OPTIONS:
--config A Hardhat config file.
--emoji Use emoji in messages.
--help Shows this message, or a task's help if its name is provided
--max-memory The maximum amount of memory that Hardhat can use.
--network The network to connect to.
--show-stack-traces Show stack traces.
--tsconfig A TypeScript config file.
--verbose Enables Hardhat verbose logging
--version Shows hardhat's version.
AVAILABLE TASKS:
check Check whatever you need
clean Clears the cache and deletes all artifacts
compile Compiles the entire project, building all artifacts
console Opens a hardhat console
coverage Generates a code coverage report for tests
flatten Flattens and prints contracts and their dependencies
help Prints this message
node Starts a JSON-RPC server on top of Hardhat Network
run Runs a user-defined script after compiling the project
test Runs mocha tests
typechain Generate Typechain typings for compiled contracts
verify Verifies contract on Etherscan
To get help for a specific task run: npx hardhat help [task]
The list of available tasks includes the built-in ones and also those that came with any installed plugins. npx hardhat is your starting point to find out what tasks are available to run.
4. Compiling your contracts
Next, if you take a look in the contracts/ folder, you'll see Lock.sol:
// SPDX-License-Identifier: UNLICENSEDpragmasolidity ^0.8.9;// Uncomment this line to use console.log// import "hardhat/console.sol";contract Lock {uintpublic unlockTime;addresspayablepublic owner;eventWithdrawal(uint amount, uint when);constructor(uint_unlockTime) payable {require( block.timestamp < _unlockTime,"Unlock time should be in the future" ); unlockTime = _unlockTime; owner =payable(msg.sender); }functionwithdraw() public {// Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal// console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp);require(block.timestamp >= unlockTime,"You can't withdraw yet");require(msg.sender == owner,"You aren't the owner");emitWithdrawal(address(this).balance, block.timestamp); owner.transfer(address(this).balance); }}
To compile it, simply run:
npx hardhat compile
If you created a TypeScript project, this task will also generate TypeScript bindings using TypeChain.
If you take a look in the test/ folder, you'll see a test file:
import { time, loadFixture,} from"@nomicfoundation/hardhat-toolbox/network-helpers";import { anyValue } from"@nomicfoundation/hardhat-chai-matchers/withArgs";import { expect } from"chai";import { ethers } from"hardhat";describe("Lock",function () {// We define a fixture to reuse the same setup in every test.// We use loadFixture to run this setup once, snapshot that state,// and reset Hardhat Network to that snapshot in every test.asyncfunctiondeployOneYearLockFixture() {constONE_YEAR_IN_SECS=365*24*60*60;constONE_GWEI=1_000_000_000;constlockedAmount=ONE_GWEI;constunlockTime= (awaittime.latest()) +ONE_YEAR_IN_SECS;// Contracts are deployed using the first signer/account by defaultconst [owner,otherAccount] =awaitethers.getSigners();constLock=awaitethers.getContractFactory("Lock");constlock=awaitLock.deploy(unlockTime, { value: lockedAmount });return { lock, unlockTime, lockedAmount, owner, otherAccount }; }describe("Deployment",function () {it("Should set the right unlockTime",asyncfunction () {const { lock,unlockTime } =awaitloadFixture(deployOneYearLockFixture);expect(awaitlock.unlockTime()).to.equal(unlockTime); });it("Should set the right owner",asyncfunction () {const { lock,owner } =awaitloadFixture(deployOneYearLockFixture);expect(awaitlock.owner()).to.equal(owner.address); });it("Should receive and store the funds to lock",asyncfunction () {const { lock,lockedAmount } =awaitloadFixture( deployOneYearLockFixture );expect(awaitethers.provider.getBalance(lock.target)).to.equal( lockedAmount ); });it("Should fail if the unlockTime is not in the future",asyncfunction () {// We don't use the fixture here because we want a different deploymentconstlatestTime=awaittime.latest();constLock=awaitethers.getContractFactory("Lock");awaitexpect(Lock.deploy(latestTime, { value:1 })).to.be.revertedWith("Unlock time should be in the future" ); }); });describe("Withdrawals",function () {describe("Validations",function () {it("Should revert with the right error if called too soon",asyncfunction () {const { lock } =awaitloadFixture(deployOneYearLockFixture);awaitexpect(lock.withdraw()).to.be.revertedWith("You can't withdraw yet" ); });it("Should revert with the right error if called from another account",asyncfunction () {const { lock,unlockTime,otherAccount } =awaitloadFixture( deployOneYearLockFixture );// We can increase the time in Hardhat Networkawaittime.increaseTo(unlockTime);// We use lock.connect() to send a transaction from another accountawaitexpect(lock.connect(otherAccount).withdraw()).to.be.revertedWith("You aren't the owner" ); });it("Shouldn't fail if the unlockTime has arrived and the owner calls it",asyncfunction () {const { lock,unlockTime } =awaitloadFixture( deployOneYearLockFixture );// Transactions are sent using the first signer by defaultawaittime.increaseTo(unlockTime);awaitexpect(lock.withdraw()).not.to.be.reverted; }); });describe("Events",function () {it("Should emit an event on withdrawals",asyncfunction () {const { lock,unlockTime,lockedAmount } =awaitloadFixture( deployOneYearLockFixture );awaittime.increaseTo(unlockTime);awaitexpect(lock.withdraw()) .to.emit(lock,"Withdrawal").withArgs(lockedAmount, anyValue); // We accept any value as `when` arg }); });describe("Transfers",function () {it("Should transfer the funds to the owner",asyncfunction () {const { lock,unlockTime,lockedAmount,owner } =awaitloadFixture( deployOneYearLockFixture );awaittime.increaseTo(unlockTime);awaitexpect(lock.withdraw()).to.changeEtherBalances( [owner, lock], [lockedAmount,-lockedAmount] ); }); }); });});
You can run your tests with npx hardhat test:
$ npx hardhat testGenerating typings for:2 artifacts in dir: typechain-types for target: ethers-v6Successfully generated 6 typings!Compiled 2 Solidity files successfully Lock Deployment ✔ Should set the right unlockTime (610ms) ✔ Should set the right owner ✔ Should receive and store the funds to lock ✔ Should fail if the unlockTime is not in the future Withdrawals Validations ✔ Should revert with the right error if called too soon ✔ Should revert with the right error if called from another account ✔ Shouldn't fail if the unlockTime has arrived and the owner calls it Events ✔ Should emit an event on withdrawals Transfers ✔ Should transfer the funds to the owner9passing (790ms)
6. Deploying your contracts
Next, to deploy the contract we will use a Hardhat script.
Inside the scripts/ folder you will find a file with the following code:
import { ethers } from"hardhat";asyncfunctionmain() {constcurrentTimestampInSeconds=Math.round(Date.now() /1000);constunlockTime= currentTimestampInSeconds +60;constlockedAmount=ethers.parseEther("0.001");constlock=awaitethers.deployContract("Lock", [unlockTime], { value: lockedAmount, });awaitlock.waitForDeployment();console.log(`Lock with ${ethers.formatEther( lockedAmount )}ETH and unlock timestamp ${unlockTime} deployed to ${lock.target}` );}// We recommend this pattern to be able to use async/await everywhere// and properly handle errors.main().catch((error) => {console.error(error);process.exitCode =1;});
You can run it using npx hardhat run scripts/deploy.ts:
$ npx hardhat run scripts/deploy.ts
Lock with 1 ETH deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
7. Connecting a wallet or Dapp to Hardhat Network
By default, Hardhat will spin up a new in-memory instance of Hardhat Network on startup. It's also possible to run Hardhat Network in a standalone fashion so that external clients can connect to it. This could be a wallet, your Dapp front-end, or a script.
To run Hardhat Network in this way, run npx hardhat node:
$ npx hardhat node
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
This will expose a JSON-RPC interface to Hardhat Network. To use it connect your wallet or application to http://127.0.0.1:8545.