Create HelloWorld Contract Using Hardhat โ
See the full GitHub Project Code Repository.
This developer guide will walk you through setting up a new Solidity contract, configuring the Berachain network details, deploying to Berachain, and verifying the contract, all with Hardhat.
Requirements โ
Before beginning, make sure you have the following installed or setup on your computer before hand.
- NVM or Node
v18.18.2
pnpm
,yarn
, ornpm
- Wallet that contains
BERA
token (for deployment)
Creating HelloWorld Project Code Setup โ
Start by creating a new project folder for the project:
mkdir create-helloworld-contract-using-hardhat;
cd create-helloworld-contract-using-hardhat;
Initiate Hardhat
to build out the viem
template with the following prompts:
# FROM ./create-helloworld-contract-using-hardhat;
npx hardhat init;
# [Expected Prompts]:
# 888 888 888 888 888
# 888 888 888 888 888
# 888 888 888 888 888
# 8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
# 888 888 "88b 888P" d88" 888 888 "88b "88b 888
# 888 888 .d888888 888 888 888 888 888 .d888888 888
# 888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
# 888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
#
# ๐ท Welcome to Hardhat v2.18.3 ๐ทโ
#
# โ What do you want to do? ยท Create a TypeScript project (with Viem)
# โ Hardhat project root: ยท /path/to/create-helloworld-contract-using-hardhat
# โ Do you want to add a .gitignore? (Y/n) ยท y
# โ Do you want to install this sample project's dependencies with npm (hardhat @nomicfoundation/# hardhat-toolbox-viem)? (Y/n) ยท y
We'll need one more dependency for our project, which is dotenv
to take advantage of environment variables.
# FROM ./create-helloworld-contract-using-hardhat;
pnpm add -D dotenv;
Creating the HelloWorld Contract โ
Now that we have everything, there should be some existing files that were created by Hardhat.
We'll start by creating a new Solidity contract by renaming the existing to the following:
# FROM ./create-helloworld-contract-using-hardhat;
# Renames `Lock.sol` to `HelloWorld.sol`
mv contracts/Lock.sol contracts/HelloWorld.sol;
In our new HelloWorld.sol
file, we'll replace the existing code with the following:
File: ./contract/HelloWorld.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
contract HelloWorld {
// Events that allows for emitting a message
event NewGreeting(address sender, string message);
// Variables
string greeting;
// Main constructor run at deployment
constructor(string memory _greeting) {
greeting = _greeting;
emit NewGreeting(msg.sender, _greeting);
}
// Get function
function getGreeting() public view returns (string memory) {
return greeting;
}
// Set function
function setGreeting(string memory _greeting) public {
greeting = _greeting;
emit NewGreeting(msg.sender, _greeting);
}
}
Now that we have our Solidity contract, let's make sure it can compile correctly.
# FROM ./create-helloworld-contract-using-hardhat;
npx hardhat compile;
# [Expected Output]:
# Compiled 1 Solidity file successfully (evm target: paris).
Let's make this step a bit easier by adding this command to our package.json
File: ./package.json
{
"name": "create-helloworld-contract-using-hardhat",
"scripts": {
"compile": "./node_modules/.bin/hardhat compile"
},
"devDependencies": {
"@nomicfoundation/hardhat-toolbox-viem": "^1.0.0",
"dotenv": "^16.3.1",
"hardhat": "^2.18.3"
}
}
We can take advantage of compiling with the following command:
# FROM ./create-helloworld-contract-using-hardhat;
pnpm compile; # npm run compile or yarn run compil;
# [Expected Output]:
# Nothing to compile
Testing the HelloWorld Contract โ
Now that we have our contract, let's make sure it's working correctly by writting some tests.
Hardhat already created a test file in our /test
directory, and we're going to rename it.
# FROM ./create-helloworld-contract-using-hardhat;
# Renames `Lock.ts` to `HelloWorld.test.ts`
mv test/Lock.ts test/HellWorld.test.ts;
In our new HelloWorld.test.ts
file, we'll replace the existing code with the following:
File: ./test/HelloWorld.test.ts
// Imports
// ========================================================
import { loadFixture } from "@nomicfoundation/hardhat-toolbox-viem/network-helpers";
import { expect } from "chai";
import hre from "hardhat";
// Tests
// ========================================================
describe("HelloWorld", 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.
async function deployFixture() {
// Contracts are deployed using the first signer/account by default
const [owner, otherAccount] = await hre.viem.getWalletClients();
const contract = await hre.viem.deployContract("HelloWorld", [
"Test Message",
]);
const publicClient = await hre.viem.getPublicClient();
return {
owner,
otherAccount,
publicClient,
contract,
};
}
/**
*
*/
describe("Deployment", function () {
/**
*
*/
it("Should deploy with original message", async function () {
// Setup
const { contract } = await loadFixture(deployFixture);
// Init + Expectations
expect(await contract.read.getGreeting()).to.equal("Test Message");
});
/**
*
*/
it("Should set a new message", async function () {
// Setup
const { contract, owner } = await loadFixture(deployFixture);
// Init
await contract.write.setGreeting(["Hello There"]);
// Expectations
expect(await contract.read.getGreeting()).to.equal("Hello There");
});
});
});
With our new tests defined, let's run our tests with the following command:
# FROM ./create-helloworld-contract-using-hardhat;
npx hardhat test;
# [Expected Output]:
# HelloWorld
# Deployment
# โ Should deploy with original message (2723ms)
# โ Should set a new message
#
#
# 2 passing (3s)
Let's make this step a bit easier again by adding this command to our package.json
so that we can easily run pnpm test
,
File: ./package.json
{
"name": "create-helloworld-contract-using-hardhat",
"scripts": {
"compile": "./node_modules/.bin/hardhat compile",
"test": "./node_modules/.bin/hardhat test"
},
"devDependencies": {
"@nomicfoundation/hardhat-toolbox-viem": "^1.0.0",
"dotenv": "^16.3.1",
"hardhat": "^2.18.3"
}
}
Configuring Hardhat for Berachain Contract Deployment โ
NOTE: Hardhat with viem doesn't fully support custom chains out of the box yet, but this will be supported later when Berachain is launched.
In order to get our hardhat.config.ts
setup correctly, let's take advantage of the dotenv
package we installed by creating a .env
file which will allow us to declare environment variables for our configuration to read.
# FROM ./create-helloworld-contract-using-hardhat;
touch .env;
In our new .env
let's enter the following information:
NOTE: These values are subject to change, but the overall configuration is the same.
File: ./.env
# Chain Configurations
CHAIN_ID=80084
NETWORK_NAME="berachainbArtio"
CURRENCY_DECIMALS=18
CURRENCY_NAME="BERA Token"
CURRENCY_SYMBOL="BERA"
# API key for Beratrail Block Explorer, can be any value for now
BLOCK_EXPLORER_NAME=Beratrail Block Explorer
BLOCK_EXPLORER_API_KEY=xxxxx
BLOCK_EXPLORER_API_URL=https://api.routescan.io/v2/network/testnet/evm/80084/etherscan/api/
BLOCK_EXPLORER_URL=https://bartio.beratrail.io/
# Wallet + RPC configurations
RPC_URL=https://bartio.rpc.berachain.com/
# Private key generated from Hardhat local - replace with Berachain
# NEVER SHARE THIS WITH ANYONE AND AVOID COMMITTING THIS WITH YOUR GIT REPOSITORY
WALLET_PRIVATE_KEY=0xYOUR_WALLET_PRIVATE_KEY
With our environment variable setup, we can now load them into our hardhat.config.ts
with the following code:
File: ./hardhat.config.ts
// Imports
// ========================================================
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox-viem";
import dotenv from "dotenv";
// Load Environment Variables
// ========================================================
dotenv.config();
// Main Hardhat Config
// ========================================================
const config: HardhatUserConfig = {
solidity: "0.8.19",
networks: {
// For localhost network
hardhat: {
chainId: 1337,
},
// NOTE: hardhat viem currently doesn't yet support this method for custom chains through Hardhat config โด
berachainTestnet: {
chainId: parseInt(`${process.env.CHAIN_ID}`),
url: `${process.env.RPC_URL || ""}`,
accounts: process.env.WALLET_PRIVATE_KEY
? [`${process.env.WALLET_PRIVATE_KEY}`]
: [],
},
},
};
// Exports
// ========================================================
export default config;
Deploying HelloWorld Contract โ
Now that we have the configuration setup, let's try running a local node and deploying it there.
But first, let's make it a bit easier by making some modifications to our package.json
to make these commands a bit easier to run.
File: ./package.json
{
"name": "create-helloworld-contract-using-hardhat",
"scripts": {
"compile": "./node_modules/.bin/hardhat compile",
"node": "./node_modules/.bin/hardhat node",
"deploy:localhost": "./node_modules/.bin/hardhat run scripts/deploy.ts --network localhost",
"deploy:berachain": "./node_modules/.bin/hardhat run scripts/deploy.ts --network berachainTestnet",
"test": "./node_modules/.bin/hardhat test"
},
"devDependencies": {
"@nomicfoundation/hardhat-toolbox-viem": "^1.0.0",
"dotenv": "^16.3.1",
"hardhat": "^2.18.3"
}
}
We'll also need to configure our ./scripts/deploy.ts
script to make sure things are deployed correctly.
File: ./scripts/deploy.ts
// Imports
// ========================================================
import hre from "hardhat";
// Main Deployment Script
// ========================================================
async function main() {
const contract = await hre.viem.deployContract("HelloWorld", [
"Hello from the contract!",
]);
console.log(`HelloWorld deployed to ${contract.address}`);
}
// Init
// ========================================================
// 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;
});
With our deployment script set, let's run a local node in one Terminal and deploy the contract in another Terminal.
Terminal 1
# FROM ./create-helloworld-contract-using-hardhat;
pnpm node;
# [Expected Output]:
# Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
#
# Accounts
# ========
#
# WARNING: These accounts, and their private keys, are publicly known.
# Any funds sent to them on Mainnet or any other live network WILL BE LOST.
#
# Account #0: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000 ETH)
# Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
Copy the Private Key and paste it our .env
file for the WALLET_PRIVATE_KEY
File: ./.env
WALLET_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
Teminal 2
# FROM ./create-helloworld-contract-using-hardhat;
pnpm deploy:localhost;
# [Expected Similar Output]:
# HelloWorld deployed to 0x5fbdb2315678afecb367f032d93f642f64180aa3
Now that we can see that the contract can be successfully deployed for a local node, let's configure our deployment script to take advantage of deploying directly to Berachain.
NOTE: Custom configurations are needed for viem to support custom chains. This will show how set that up with Berachain. When Berachain is public, these extra configurations might not be needed.
File: ./scripts/deploy.ts
// Imports
// ========================================================
import hre from "hardhat";
import fs from "fs";
import { defineChain } from "viem";
import { privateKeyToAccount } from "viem/accounts";
// Config Needed For Custom Chain
// ========================================================
const chainConfiguration = defineChain({
id: parseInt(`${process.env.CHAIN_ID}`),
name: `${process.env.NETWORK_NAME}`,
network: `${process.env.NETWORK_NAME}`,
nativeCurrency: {
decimals: parseInt(`${process.env.CURRENCY_DECIMALS}`),
name: `${process.env.CURRENCY_NAME}`,
symbol: `${process.env.CURRENCY_SYMBOL}`,
},
rpcUrls: {
default: {
http: [`${process.env.RPC_URL}`],
},
public: {
http: [`${process.env.RPC_URL}`],
},
},
blockExplorers: {
default: {
name: `${process.env.BLOCK_EXPLORER_NAME}`,
url: `${process.env.BLOCK_EXPLORER_URL}`,
},
},
});
// Main Deployment Script
// ========================================================
async function main() {
// NOTE: hardhat with viem currently doesn't support custom chains so there needs to be some custom functionality โด
if (hre.network.name === "berachainTestnet") {
// Retrieve contract artifact ABI & Bytecode
const contractName = "HelloWorld";
const artifactFile = fs.readFileSync(
`${hre.artifacts._artifactsPath}/contracts/${contractName}.sol/${contractName}.json`
);
const artifactJSON = JSON.parse(artifactFile.toString()) as any;
// Configure wallet client
const walletClient = await hre.viem.getWalletClient(
// wallet account
privateKeyToAccount(hre.network.config.accounts?.[0] as `0x${string}`),
// configured chain
{
chain: chainConfiguration,
}
);
// Deploy contract
const hash = await walletClient.deployContract({
abi: artifactJSON.abi,
bytecode: artifactJSON.bytecode,
args: ["Hello From Deployed Contract"],
});
console.log({ hash });
// Retrieve deployed contract address
const publicClient = await hre.viem.getPublicClient({
chain: chainConfiguration,
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log(`${contractName} deployed to ${receipt?.contractAddress}`);
} else {
const contract = await hre.viem.deployContract("HelloWorld", [
"Hello from the contract!",
]);
console.log(`HelloWorld deployed to ${contract.address}`);
}
}
// Init
// ========================================================
// 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;
});
Now replace your WALLET_PRIVATE_KEY
with a wallet that has BERA tokens. You can also get Testnet BERA tokens from the Berachain Testnet Faucet.
File: ./.env
WALLET_PRIVATE_KEY=0xYOUR_WALLET_PRIVATE_KEY
Now let's deploy our contract to Berachain directly.
# FROM ./create-helloworld-contract-using-hardhat;
pnpm deploy:berachain;
# [Expected Similar Output]:
# {
# hash: '0x3ff0120c126b20d9f286657521c9d2d1edbb38f60dcd5fc6b95638a192588182'
# }
# HelloWorld deployed to 0x38f8423cc4390938c01616d7a9f761972e7f116a
We can also see our deployed contract in the Berachain Beratrail Block Explorer by going to the following address:
open https://bartio.beratrail.io/address/0x38f8423cc4390938c01616d7a9f761972e7f116a
# [Expected Result Should Open Your Browser]
You'll see that our contract has been successfully deployed but not verified as it still shows the contract bytecode.
Verifying HelloWorld Contract โ
To verify our contract, we just need add an additional configuration to our hardhat.config.ts
file.
File: ./hardhat.config.ts
// Imports
// ========================================================
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox-viem";
import dotenv from "dotenv";
// Load Environment Variables
// ========================================================
dotenv.config();
// Main Hardhat Config
// ========================================================
const config: HardhatUserConfig = {
solidity: "0.8.19",
networks: {
hardhat: {
chainId: 1337,
},
// NOTE: hardhat viem currently doesn't yet support this method for custom chains through Hardhat config โด
berachainTestnet: {
chainId: parseInt(`${process.env.CHAIN_ID}`),
url: `${process.env.RPC_URL || ""}`,
accounts: process.env.WALLET_PRIVATE_KEY
? [`${process.env.WALLET_PRIVATE_KEY}`]
: [],
},
},
// For Contract Verification
etherscan: {
apiKey: `${process.env.BLOCK_EXPLORER_API_KEY}`,
customChains: [
{
network: "Berachain Testnet",
chainId: parseInt(`${process.env.CHAIN_ID}`),
urls: {
apiURL: `${process.env.BLOCK_EXPLORER_API_URL}`,
browserURL: `${process.env.BLOCK_EXPLORER_URL}`,
},
},
],
},
};
// Exports
// ========================================================
export default config;
Now that we have the configuration setup, let's add another run command to our package.json
File: ./package.json
{
"name": "create-helloworld-contract-using-hardhat",
"scripts": {
"compile": "./node_modules/.bin/hardhat compile",
"node": "./node_modules/.bin/hardhat node",
"deploy:localhost": "./node_modules/.bin/hardhat run scripts/deploy.ts --network localhost",
"deploy:berachain": "./node_modules/.bin/hardhat run scripts/deploy.ts --network berachainTestnet",
"test": "./node_modules/.bin/hardhat test",
"verify": "./node_modules/.bin/hardhat verify --network berachainTestnet"
},
"devDependencies": {
"@nomicfoundation/hardhat-toolbox-viem": "^1.0.0",
"dotenv": "^16.3.1",
"hardhat": "^2.18.3"
}
}
With our previously deploy contract address and taking into account that we deployed our contract with the initial argument of "Hello From Deployed Contract"
, let's run the following to confirm our contract.
# FROM ./create-helloworld-contract-using-hardhat;
# Equivalent to: npx hardhat verify 0x38f8423cc4390938c01616d7a9f761972e7f116a "Hello From Deployed Contract"
pnpm verify 0x38f8423cc4390938c01616d7a9f761972e7f116a "Hello From Deployed Contract";
# [Expected Output]:
#
# Successfully submitted source code for contract
# contracts/HelloWorld.sol:HelloWorld at 0x38f8423cc4390938c01616d7a9f761972e7f116a
# for verification on the block explorer. Waiting for verification result...
#
# Successfully verified contract HelloWorld on the block explorer.
# https://bartio.beratrail.io/address/0x38f8423cc4390938c01616d7a9f761972e7f116a#code
We should now see on Beratail that the contract is verified and that the Solidity code is now showing.
Full Code Repository โ
The full github code repository can be found in the guides section of this repository under Create HelloWorld Contract Using Hardhat.