Skip to main content

Flow Hardhat Guide

Hardhat is an Ethereum development tool designed to facilitate the deployment, testing, and debugging of smart contracts. It provides a streamlined experience for developers working with Solidity contracts.

Prerequisites

Node

Node v18 or higher, available for download here.

For those new to Hardhat, we recommend exploring the official documentation to get acquainted. The following instructions utilize npm to initialize a project and install dependencies:

Wallet

You'll also need a wallet that supports EVM. For this guide, a MetaMask account and its corresponding private key will work.


_10
mkdir hardhat-example
_10
cd hardhat-example
_10
_10
npm init
_10
_10
npm install --save-dev hardhat
_10
_10
npx hardhat init

When prompted, select TypeScript and to use @nomicfoundation/hardhat-toolbox to follow along with this guide.

Fund Your Wallet

To deploy smart contracts, ensure your wallet has $FLOW. Obtain funds by navigating to the Flow Faucet and entering your wallet address.

Deploying a Smart Contract with Hardhat

This section guides you through the process of deploying smart contracts on the Flow network using Hardhat.

Configuration

First, incorporate the Testnet network into your hardhat.config.ts:


_15
import { HardhatUserConfig } from 'hardhat/config';
_15
import '@nomicfoundation/hardhat-toolbox';
_15
_15
const config: HardhatUserConfig = {
_15
solidity: '0.8.24',
_15
networks: {
_15
testnet: {
_15
url: 'https://testnet.evm.nodes.onflow.org',
_15
accounts: [`<PRIVATE_KEY>`], // In practice, this should come from an environment variable and not be commited
_15
gas: 500000, // Example gas limit
_15
},
_15
},
_15
};
_15
_15
export default config;

To keep this example straightforward, we've included the account's private key directly in hardhat.config.ts. However, it is crucial to avoid committing private keys to your Git repository for security reasons. Instead, opt for using environment variables for safer handling of sensitive information.

Deploying HelloWorld Smart Contract

HelloWorld Smart Contract


_25
// SPDX-License-Identifier: MIT
_25
pragma solidity ^0.8.0;
_25
_25
contract HelloWorld {
_25
// Declare a public field of type string.
_25
string public greeting;
_25
_25
// Constructor to initialize the greeting.
_25
// In Solidity, the constructor is defined with the "constructor" keyword.
_25
constructor() {
_25
greeting = "Hello, World!";
_25
}
_25
_25
// Public function to change the greeting.
_25
// The "public" keyword makes the function accessible from outside the contract.
_25
function changeGreeting(string memory newGreeting) public {
_25
greeting = newGreeting;
_25
}
_25
_25
// Public function that returns the greeting.
_25
// In Solidity, explicit return types are declared.
_25
function hello() public view returns (string memory) {
_25
return greeting;
_25
}
_25
}

Deploying:

  1. Create a file named HelloWorld.sol under contracts directory.
  2. Add above HelloWorld.sol contract code to new file.
  3. Create a deploy.ts file in scripts directory.
  4. Paste in the following TypeScript code.

_18
import { ethers } from 'hardhat';
_18
_18
async function main() {
_18
const [deployer] = await ethers.getSigners();
_18
_18
console.log('Deploying contracts with the account:', deployer.address);
_18
_18
const deployment = await ethers.deployContract('HelloWorld');
_18
_18
console.log('HelloWorld address:', await deployment.getAddress());
_18
}
_18
_18
main()
_18
.then(() => process.exit(0))
_18
.catch((error) => {
_18
console.error(error);
_18
process.exit(1);
_18
});

  1. Run npx hardhat run scripts/deploy.ts --network testnet in the project root.
  2. Copy the deployed HelloWorld address. This address will be used in other scripts.

Output should look like this (with the exception that your address will be different):


_10
❯ npx hardhat run scripts/deploy.ts --network testnet
_10
Deploying contracts with the account: ...
_10
HelloWorld address: 0x3Fe94f43Fb5CdB8268A801f274521a07F7b99dfb

You can now search for your deployed contract on the Flowscan block explorer!

Get HelloWorld Contract Greeting

Now, we want to get the greeting from the deployed HelloWorld smart contract.


_23
import { ethers } from 'hardhat';
_23
import HelloWorldABI from '../artifacts/contracts/HelloWorld.sol/HelloWorld.json';
_23
_23
async function main() {
_23
// Replace with your contract's address
_23
const contractAddress = '0x3Fe94f43Fb5CdB8268A801f274521a07F7b99dfb';
_23
// Get hardhat provider
_23
const provider = ethers.provider;
_23
// Create a new contract instance
_23
const helloWorldContract = new ethers.Contract(
_23
contractAddress,
_23
HelloWorldABI.abi,
_23
provider,
_23
);
_23
// Call the greeting function
_23
const greeting = await helloWorldContract.hello();
_23
console.log('The greeting is:', greeting);
_23
}
_23
_23
main().catch((error) => {
_23
console.error(error);
_23
process.exit(1);
_23
});

Steps:

  1. Create a getGreeting.ts file in the scripts directory.
  2. Paste contents of script above. Make sure to update the contract address with the one from deployment in earlier step.
  3. Call script to get the greeting, npx hardhat run scripts/getGreeting.ts --network testnet
  4. The output should be as follows:

_10
❯ npx hardhat run scripts/getGreeting.ts --network testnet
_10
The greeting is: Hello, World!

Update Greeting on HelloWorld Smart Contract

Next, we'll add a script to update the greeting and log it.


_38
import { ethers } from 'hardhat';
_38
import HelloWorldABI from '../artifacts/contracts/HelloWorld.sol/HelloWorld.json';
_38
_38
async function main() {
_38
const contractAddress = '0x3Fe94f43Fb5CdB8268A801f274521a07F7b99dfb';
_38
_38
const newGreeting = process.env.NEW_GREETING;
_38
if (!newGreeting) {
_38
console.error('Please set the NEW_GREETING environment variable.');
_38
process.exit(1);
_38
}
_38
_38
// Signer to send the transaction (e.g., the first account from the hardhat node)
_38
const [signer] = await ethers.getSigners();
_38
_38
// Contract instance with signer
_38
const helloWorldContract = new ethers.Contract(
_38
contractAddress,
_38
HelloWorldABI.abi,
_38
signer,
_38
);
_38
_38
console.log('The greeting is:', await helloWorldContract.hello());
_38
_38
// Create and send the transaction
_38
const tx = await helloWorldContract.changeGreeting(newGreeting);
_38
console.log('Transaction hash:', tx.hash);
_38
_38
// Wait for the transaction to be mined
_38
await tx.wait().catch((error: Error) => {});
_38
console.log('Greeting updated successfully!');
_38
console.log('The greeting is:', await helloWorldContract.hello());
_38
}
_38
_38
main().catch((error) => {
_38
console.error(error);
_38
process.exit(1);
_38
});

Here are the steps to follow:

  1. Create an updateGreeting.ts script in the scripts directory.
  2. Paste in the TypeScript above, make sure to update the contract address with the one from deployment in earlier step.
  3. Call the new script, NEW_GREETING='Howdy!' npx hardhat run ./scripts/updateGreeting.ts --network testnet
  4. The output should be

_10
❯ NEW_GREETING='Howdy!' npx hardhat run ./scripts/updateGreeting.ts --network testnet
_10
The greeting is: Hello, World!
_10
Transaction hash: 0x03136298875d405e0814f54308390e73246e4e8b4502022c657f04f3985e0906
_10
Greeting updated successfully!
_10
The greeting is: Howdy!

Verifying Contract

To verify your contract on Flowscan, you can update your Hardhat config file as such including the correct chainID, apiURL and browserURL:


_37
import { HardhatUserConfig } from 'hardhat/config';
_37
import '@nomicfoundation/hardhat-toolbox';
_37
import "@nomicfoundation/hardhat-verify";
_37
_37
const PRIVATE_KEY = vars.get("EVM_PRIVATE_KEY");
_37
_37
const config: HardhatUserConfig = {
_37
solidity: '0.8.24',
_37
networks: {
_37
testnet: {
_37
url: 'https://testnet.evm.nodes.onflow.org',
_37
accounts: [PRIVATE_KEY], // In practice, this should come from an environment variable and not be commited
_37
gas: 500000, // Example gas limit
_37
},
_37
},
_37
etherscan: {
_37
apiKey: {
_37
// Is not required by blockscout. Can be any non-empty string
_37
'testnet': "abc"
_37
},
_37
customChains: [
_37
{
_37
network: "testnet",
_37
chainId: 545,
_37
urls: {
_37
apiURL: "https://evm-testnet.flowscan.io/api",
_37
browserURL: "https://evm-testnet.flowscan.io/",
_37
}
_37
}
_37
]
_37
},
_37
sourcify: {
_37
enabled: false
_37
}
_37
};
_37
_37
export default config;

The verify plugin requires you to include constructor arguments with the verify task and ensures that they correspond to expected ABI signature. However, Blockscout ignores those arguments, so you may specify any values that correspond to the ABI. Execute the following command to verify the contract:


_10
npx hardhat verify --network testnet DEPLOYED_CONTRACT_ADDRESS "Constructor argument 1"