Table of contents
- Create the project structure
- Configure Hardhat
- Write the contract
- Compile the contract
- Create a deployment script
- Run a local network
- Deploy the contract
- Interact with the contract
Smart contracts are self-executing programs that run on a blockchain (loosely speaking). Once deployed, they enforce the rules you encode without needing a central authority.
My goal with this tutorial is to demonstrate the basics of a smart contract by creating a simple example of one.
I will be using the following:
- Solidity [↗]: The programming language for writing smart contracts.
- Hardhat [↗]: An Ethereum development Kit
- A local Ethereum network to mimic the live Ethereum [↗] network (The mainnet [→]).
The rest of our code will be written in Javascript (NodeJS 25 or greater).
Create the project structure
01: mkdir smart-contract-intro
02: cd smart-contract-intro
03: npm init -y
04: npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
05: npx hardhat --initRunning npx hardhat --init will prompt you with some questions
- Hardhat version: Select the latest (e.g: 3).
- Project path:
.(Use this if you are within the project directory else provide the full path). - Project type: Minimal.
- Select Yes for the remaining questions or otherwise if you are familiar with the questions.
Configure Hardhat
The default configuration points to the in-memory Hardhat network. Keep that for now and add a Solidity compiler version that matches the contract we will write.
01: // hardhat.config.ts
02: require("@nomicfoundation/hardhat-toolbox");
03:
04: module.exports = {
05: solidity: "0.8.28", // latest version as of time of writing
06: };Write the contract
Replace the generated contract with a simple storage example. It allows anyone to save and read a number.
01: // contracts/Counter.sol
02: // SPDX-License-Identifier: MIT
03: pragma solidity ^0.8.28;
04:
05: contract Counter {
06: uint256 private value;
07:
08: function current() external view returns (uint256) {
09: return value;
10: }
11:
12: function increment(uint256 amount) external {
13: value += amount;
14: }
15: }The contract exposes two public functions: current returns the stored number and increment adds to it. Solidity automatically generates the [ABI]() you will use when interacting with the deployed contract.
Compile the contract
Compile to confirm the Solidity code is valid.
$ npx hardhat compileHardhat outputs the build artifacts in the
artifacts/directory, including the ABI and bytecode.
Create a deployment script
Create a file deploy.js and add in the code below.
01: // scripts/deploy.js
02: const hre = require("hardhat");
03:
04: async function main() {
05: const Counter = await hre.ethers.getContractFactory("Counter");
06: const counter = await Counter.deploy();
07: await counter.waitForDeployment();
08: console.log("Counter deployed to:", counter.target);
09: }
10:
11: main()
12: .then(() => process.exit(0))
13: .catch((error) => {
14: console.error(error);
15: process.exit(1);
16: });hre.ethers.getContractFactory prepares the contract for deployment, deploy publishes it to the network, and waitForDeployment waits until the transaction is mined.
Run a local network
Hardhat can run an ephemeral blockchain that resets each time you start it. Use one terminal tab to run the node.
$ npx hardhat nodeThe command prints funded accounts and their private keys. Keep this terminal open so the chain keeps running.
Deploy the contract
Open another terminal tab in the project directory and run the deployment script against the local node.
$ npx hardhat run scripts/deploy.js --network localhostYou should see the deployed contract address in the output. The deployment transaction consumes one of the prefunded accounts listed earlier.
Interact with the contract
Hardhat’s console attaches to the running node and lets you call the contract methods.
$ npx hardhat console --network localhostInside the console, paste the following lines to interact with the contract.
01: const [account] = await ethers.getSigners();
02: const Counter = await ethers.getContractFactory("Counter");
03: const counter = Counter.attach("<DEPLOYED_ADDRESS>");
04: await counter.connect(account).increment(5);
05: (await counter.current()).toString();Replace with the address printed during deployment. The final line should return 5, showing that the state changed on-chain.
Here is another article you might like 😊 Getting Started With Smart Contracts