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.
_10mkdir hardhat-example_10cd hardhat-example_10_10npm init_10_10npm install --save-dev hardhat_10_10npx 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
:
_15import { HardhatUserConfig } from 'hardhat/config';_15import '@nomicfoundation/hardhat-toolbox';_15_15const 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_15export 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_25pragma solidity ^0.8.0;_25_25contract 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:
- Create a file named
HelloWorld.sol
undercontracts
directory. - Add above
HelloWorld.sol
contract code to new file. - Create a
deploy.ts
file inscripts
directory. - Paste in the following TypeScript code.
_18import { ethers } from 'hardhat';_18_18async 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_18main()_18 .then(() => process.exit(0))_18 .catch((error) => {_18 console.error(error);_18 process.exit(1);_18 });
- Run
npx hardhat run scripts/deploy.ts --network testnet
in the project root. - 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_10Deploying contracts with the account: ..._10HelloWorld 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.
_23import { ethers } from 'hardhat';_23import HelloWorldABI from '../artifacts/contracts/HelloWorld.sol/HelloWorld.json';_23_23async 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_23main().catch((error) => {_23 console.error(error);_23 process.exit(1);_23});
Steps:
- Create a
getGreeting.ts
file in thescripts
directory. - Paste contents of script above. Make sure to update the contract address with the one from deployment in earlier step.
- Call script to get the greeting,
npx hardhat run scripts/getGreeting.ts --network testnet
- The output should be as follows:
_10❯ npx hardhat run scripts/getGreeting.ts --network testnet_10The greeting is: Hello, World!
Update Greeting on HelloWorld Smart Contract
Next, we'll add a script to update the greeting and log it.
_38import { ethers } from 'hardhat';_38import HelloWorldABI from '../artifacts/contracts/HelloWorld.sol/HelloWorld.json';_38_38async 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_38main().catch((error) => {_38 console.error(error);_38 process.exit(1);_38});
Here are the steps to follow:
- Create an
updateGreeting.ts
script in thescripts
directory. - Paste in the TypeScript above, make sure to update the contract address with the one from deployment in earlier step.
- Call the new script,
NEW_GREETING='Howdy!' npx hardhat run ./scripts/updateGreeting.ts --network testnet
- The output should be
_10❯ NEW_GREETING='Howdy!' npx hardhat run ./scripts/updateGreeting.ts --network testnet_10The greeting is: Hello, World!_10Transaction hash: 0x03136298875d405e0814f54308390e73246e4e8b4502022c657f04f3985e0906_10Greeting updated successfully!_10The 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:
_37import { HardhatUserConfig } from 'hardhat/config';_37import '@nomicfoundation/hardhat-toolbox';_37import "@nomicfoundation/hardhat-verify";_37_37const PRIVATE_KEY = vars.get("EVM_PRIVATE_KEY");_37_37const 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_37export 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:
_10npx hardhat verify --network testnet DEPLOYED_CONTRACT_ADDRESS "Constructor argument 1"