
Our work will be done in two programming languages: JavaScript and Solidity.
We will use the Hardhat development environment. Hardhat is a Node package which automates the creation, deployment and testing of Solidity contracts. It uses convention over configuration, with a directory structure and configuration files determined by the Hardhat environment.
We will run our code on a block-chain. Consider these three cases of blockchains:
% echo Download and install nvm % curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash % export NVM_DIR="$HOME/.nvm" % [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" % [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" % echo Download and install Node.js % nvm install 22 % node --version v22.4.1
% mkdir hello-eth
% cd hello-eth
% npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
Press ^C at any time to quit.
package name: (hello-eth) ↵
version: (1.0.0) ↵
description: ↵
entry point: (index.js) ↵
test command: ↵
git repository: ↵
keywords: ↵
author: ↵
license: (ISC) ↵
About to write to /home/ubuntu/hello-eth/package.json:
{
"name": "hello-eth",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Is this OK? (yes) ↵
% npm install --save-dev hardhat
added 60 packages, and audited 61 packages in 14s
16 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
% npx hardhat --init
Which version of Hardhat would you like to use? · hardhat-2
Where would you like to initialize the project?
Please provide either a relative or an absolute path: · .
What type of project would you like to initialize? · · empty-hardhat-config-js
You need to update the following dependencies using the following command:
npm install --save-dev "hardhat@^2.14.0"
Do you want to run it now? (Y/n) · true ↵
% npm install --save-dev "@nomicfoundation/hardhat-toolbox@hh2"
% echo now add |require("@nomicfoundation/hardhat-toolbox");| to the top off hardhat.config.js
// contracts/HelloWorld.sol
// SPDX-License-Identifier: UNLICENSED
// ^ means 0.9 etc also acceptable (~ means 0.8.25 also acceptable)
pragma solidity ^0.8.24;
contract HelloWorld {
string private _greeting = "Hello World!" ;
address private _owner ;
constructor () {
_owner = msg.sender ;
}
modifier onlyOwner() {
require (
msg.sender == _owner, "Ownable: caller is not the owner"
) ;
_ ;
}
function getGreeting() external view returns(string memory) {
return _greeting ;
}
function setGreeting(string calldata greeting) external onlyOwner {
_greeting = greeting ;
}
function owner() public view returns(address) {
return _owner ;
}
}
The compile this code with,
% npx hardhat compile
We will need the following script to deploy our contract. Write this script to deploy.js in the script directory
// scripts/helloworld-deploy.js
async function main () {
// We get the contract to deploy
const HelloWorld = await ethers.getContractFactory('HelloWorld');
console.log('Deploying HelloWorld ...');
const hello_world = await HelloWorld.deploy();
await hello_world.waitForDeployment();
console.log('HelloWorld deployed to:', await hello_world.getAddress());
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
Note: the use of async and await. Node supports asynchronous programming where tasks are launched in threads. Returning from any such launch is an object the represents the perhaps ongoing execution. This is particularly relevant in ethereum because execution on the blockchain are by volunteer nodes, and the results have to be checked and finalized, and the time for this to occur depends.
Start the blockchain in the background.
% npx hardhat node 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 Account #1: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 (10000 ETH) Private Key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d .... Account #19: 0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199 (10000 ETH) Private Key: 0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e 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. ^Z % echo control-Z and background the node process % bg
When Hardhat starts up a local node it creates wallets for several accounts and fills them with test ether. Note in the console the address of the wallerts and their private keys.
You can rejoin this thread with the command fg and that control-C to kil it.
% fg npx hardhat node ^C %
You can also kill it using the kill _pid_ command if you know the PID; which you can get by looking at the output of ps aux.
Now deploy the contract to the local chain.
$ npx hardhat run --network localhost ./scripts/helloworld-deploy.js eth_accounts hardhat_metadata (20) Deploying HelloWorld ... eth_blockNumber eth_getBlockByNumber eth_feeHistory eth_maxPriorityFeePerGas eth_sendTransaction Contract deployment: HelloWorld Contract address: 0x5fbdb2315678afecb367f032d93f642f64180aa3 Transaction: 0x734b661b8219b522852dff71e7df78fe546504aad6481dcadb556223f6b91392 From: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 Value: 0 ETH Gas used: 528107 of 16777216 Block #1: 0xf084a45e868274c4af4e93b9eab1c36b27d3e5af08e9f59c87bd5bcc5e36ff68 eth_getTransactionByHash eth_getTransactionReceipt HelloWorld deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 Deploying HelloWorld ... HelloWorld deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Then get to a console that can connect with the node, to exercise the contract,
% npx hardhat console --network localhost
Welcome to Node.js v22.4.1.
Type ".help" for more information.
> await ethers.provider.getBlockNumber()
1
> const HW = await ethers.getContractFactory('HelloWorld')
undefined
> const hw = await HW.attach("0x5fbdb2315678afecb367f032d93f642f64180aa3")
undefined
> await hw.getGreeting()
'Hello World!'
> await hw.setGreeting('Goodnight Moon')
ContractTransactionResponse {
provider: HardhatEthersProvider {
_hardhatProvider: LazyInitializationProviderAdapter {
_providerFactory: [AsyncFunction (anonymous)],
_emitter: [EventEmitter],
_initializingPromise: [Promise],
provider: [BackwardsCompatibilityProviderAdapter]
},
_networkName: 'localhost',
_blockListeners: [],
_transactionHashListeners: Map(0) {},
_eventListeners: []
},
blockNumber: 2,
blockHash: '0x6f288b0fdfcb56173bef9294240cfb31912793a1ccb5e6ec46ed6ea3f01c33b1',
index: undefined,
hash: '0xbe27e55d179ba7632cb262c26639ef0ba22dce9e475687c84ca682f7848f8b24',
type: 2,
to: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
nonce: 1,
gasLimit: 30000000n,
gasPrice: 973870221n,
maxPriorityFeePerGas: 232421875n,
maxFeePerGas: 973870221n,
maxFeePerBlobGas: null,
data: '0xa41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e68656c6c6f20657468657269756d000000000000000000000000000000000000',
value: 0n,
chainId: 31337n,
signature: Signature { r: "0x83e432c71291c5f53a6bb27d38d3cff4649555c7344c0e1b2068ebc5cc0bf308", s: "0x75960700254ce1e815c938e02b5949e2987f6ded906926049b3401496700c9f7", yParity: 0, networkV: null },
accessList: [],
blobVersionedHashes: null
}
> await ethers.provider.getBlockNumber()
2
> await hw.getGreeting()
'Goodnight Moon'
> .exit
%

author: burton rosenberg
created: 1 aug 2024
update: 7 apr 2026