DEV Community

Dustin Runnells
Dustin Runnells

Posted on

Simple Start to Smart Contract Development with Hardhat

Welcome to my post about Smart Contract development! This post is mostly just my notes on how to get a super simple smart contract (self executing program on a blockchain) written, deployed and called. For me it is easier to learn something new by doing. When dealing with blockchains and smart contracts however, that "doing" becomes expensive when you are trying to do something many times for educational purposes. Since every write to a blockchain costs something (on Ethereum Virtual Machine, or EVM, based networks, this cost is referred to as a Gas Fee). To avoid the cost of writing to a real blockchain, we'll use a tool called Hardhat. Hardhat will simulate an EVM network/blockchain for us to play with. When you are ready to actually publish your Smart Contract to a real blockchain, you'll need to either run a node yourself or use a Node Provider like Alchemy.

In this post we'll create simple contract with the Solidity programming language that just increments a state variable and lets us get it's value. For me, the most compelling part of blockchain technology is the persistence of information that it offers. In smart contracts there are Local variables that are only used to store temporary data while the contract is doing something, and State variables that are persistent and store information forever. In our example today we'll create a contract that has a function that increments a counter state variable and also has a function to retrieve it's current value.

Before we start, I want to introduce you to some terminology, you can skip this section if you are not new to the space. One key concept with blockchains is public/private keys and addresses. If you are familiar with other encryption schemes, such as PGP/GPG, this is the same idea. Every user on an EVM network has a Public Key/Address and a Private key. The private key is never shared with anyone, but your public address is visible to anyone every time you write to the blockchain. Only the owner of a private key can prove that a public address belongs to them through a process known as signing. An application (called a Wallet) on your computer or phone will store your private key and do most of the interaction with the blockchain for you. The system of smart contracts, wallets and blockchains connected with a web browser is often referred to as "Web3". A web3 application can interact with smart contracts, and since smart contracts are on the blockchain themselves and not hosted on a centralized server, these applications are often referred to as "decentralized". On a decentralized system like this, the integrity of the transaction is maintained through cryptographic proof rather than faith in a centralized authority, this idea is referred to as a "trustless" system. Some useful real-world contracts that leverage the persistence and trustless attributes of blockchain are in the Decentralized Finance (Defi) and Non-Fungible Token (NFT) spaces. But for today, we'll just use this technology to increment a counter :)

Create a Project and Setup Hardhat

Let's begin by setting up a project and the Hardhat environment. This will require you to have the Node server-side Javascript runtime already installed. Note that my notes below are based on my experience on a Linux server, but it shouldn't be too difficult to adapt to some other OS.

  1. Create a directory and node project
    mkdir counter-test
    cd counter-test/
    npm init --yes

  2. Add Hardhat to project
    npm install --save-dev hardhat

  3. Create new Hardhat project
    npx hardhat init

    Image description

  4. Remove the example contract files
    rm contracts/Lock.sol

  5. Create the Solidity contract source in a .sol file. We'll put the below in contracts/counter-test.sol

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.27;
    
    import "hardhat/console.sol";
    
    contract CounterTest {
            uint count;
    
            constructor() public {
                    count = 0;
            }
    
            function getCount() public view returns(uint) {
                    return count;
            }
    
            function incrementCount() public {
                    count = count + 1;
            }
    }
    
  6. Create a deploy script. Place the below script in sripts/deploy.js

    // We require the Hardhat Runtime Environment explicitly here. This is optional
    // but useful for running the script in a standalone fashion through `node <script>`.
    //
    // You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
    // will compile your contracts, add the Hardhat Runtime Environment's members to the
    // global scope, and execute the script.
    const hre = require("hardhat");
    
    async function main() {
      const lockedAmount = hre.ethers.parseEther("0.001");
      const lock = await hre.ethers.deployContract("CounterTest");
      await lock.waitForDeployment();
      console.log( 'Deploying CounterTest');
    }
    
    // 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;
    });
    

Start Hardhat Node and Deploy Contract

  1. Edit hardhat.config.js to include localhost:

    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
            networks: {
                    localhost: {
                            url: "http://127.0.0.1:8545",
                            chainId: 1337
                    },
                    hardhat: {
                            // See defaults
                            chainId: 1337
                    }
            },
            solidity: "0.8.27",
    };
    
  2. Start Hardhat localhost node for testing in separate terminal. This will start a Hardhat node in the foreground and display debugging information as we communicate with our smart contract from the other terminal:
    npx hardhat node

    You'll get something like the below that lists a bunch of test addresses to work with that already have some test funds:
    Image description

  3. Now that you have a local blockchain to test with, let's go back to the other terminal and deploy our smart contract with the deploy.js script that we created earlier.
    npx hardhat run scripts/deploy.js --network localhost

    If we monitor the Hardhat node we will see our attempt to deploy the smart contract. Note that the From address matches the public address of the first test account. Also make note that we now have a contract address (0x5fbdb2315678afecb367f032d93f642f64180aa3), we'll need this to interact with the contract.

    Image description

    Our contract is deployed!

  4. One final note about deployed smart contracts for this post - The web3 version of an "API" is an ABI, or Application Binary Interface. If someone wants to create an application to interact with your contract, they will need to know what functions are available. These can easily found in the ABI JSON that was generated when you deployed, take a look at artifacts/contracts/counter-test.sol/CounterTest.json . The important part here is the JSON array for ABI:

    Image description

Interact With Your Contract From Hardhat Console

Now that our contract is written and deployed on the blockchain, let's try to call some of it's functions.

  1. Start the Hardhat Javascript console

    npx hardhat console --network localhost

  2. We'll store our contract connection in the myContract variable

    const myContract = await ethers.getContractAt("CounterTest","0x5fbdb2315678afecb367f032d93f642f64180aa3")

  3. Next, let's call our getCount() function and see what the current count is:
    await myContract.getCount()
    You should get "0n" if this is our first run. Ignore the n, this is just indicates the type. You can cast this to an integer or whatever type you need in code if you were using this contract in real-life.

  4. Now, let's increment the counter with our incrementCount() function:
    await myContract.incrementCount()
    You'll see a lot more output from this call since we are actually adding information to the blockchain. Most notably you'll see the blockHash of the transaction and the gasPrice.

  5. Call getCount() again to confirm that the counter incremented as expected. The entire interaction should look something like this:
    Image description

And that's it! We have written, deployed and called a smart contract on our simulated blockchain! To turn this into a web3 application, you can explore creating a frontend that includes the ethers.js library to interact with your contract.

Top comments (0)