A Concise Hardhat Tutorial: Part 2 - ERC20 Token
Tags: blockchain, web3, solidity
Published: 2022-01-13 16:28:59

This concise hardhat tutorial has 3 sections and this is section 2. 

1 . Installation and sample project
2 . Write ERC20 token with OpenZeppelin
3 . Write ERC72 NFT token with on-chain SVG image 

Hardhat is an Ethereum development tool suite to compile, unit test,  debug and deploy smart contracts.

---

### 2. Write an ERC20 Token with OpenZeppelin

In the hardhat official tutorial at https://hardhat.org/tutorial/ , it write an ERC20 Token smart contract from scratch. Here we will write one using `OpenZeppelin` library instead. You can find more information about OpenZeppelin at: https://docs.openzeppelin.com/contracts/4.x/ . The sample contract is adapted from OpenZeppelin documents. 

![Compile-Test-Deploy circle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bql6kh6da9309rsg8alp.png) 

---

#### Step 1: Install OpenZeppelin

First, let's install OpenZeppelin contracts.

```
yarn add @openzeppelin/contracts
```

---

#### Step 2: Write an ERC20 Token with OpenZeppelin 

You can write an ERC20 Token smart contract by inheriting OpenZeppelin ERC20 implementation. [OpenZeppelin Wizard](https://docs.openzeppelin.com/contracts/4.x/wizard) is a helpful tools to setup an ERC20 token contract. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ndnjkclwaf6y1d1dlx2m.png)

Our smart contract `GLDToken.sol` goes as follows:

```solidity
// contracts/GLDToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract GLDToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("GLDToken", "GLD") {
        _mint(msg.sender, initialSupply * 10 ** decimals());
    }
}
```

Compile the contract:

```ts
yarn hardhat compile
```

---

### Step 3: Write a deploy script

Create a deploy script which is adapted from https://docs.openzeppelin.com/learn/deploying-and-interacting . You can find a detailed explanation there.

```ts
 // scripts/deploy_GLDToken.ts

import { ethers } from "hardhat";

async function main() {
  const initialSupply = 10000;

  const GLDToken = await ethers.getContractFactory("GLDToken");
  const token = await GLDToken.deploy(initialSupply);

  await token.deployed();

  const totalSupply = await token.totalSupply()

  console.log(
    `GLDToken deployed to ${token.address} with an initialSupply ${totalSupply}`
  );
}

// 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;
});
```

Using this script, we deploy GLDToken with 10000.0 GLD minted to the contract owner.

Try to deploy it to in-process local blockchain:

```bash
yarn hardhat run scripts/deploy_GLDToken.ts
//Output: 
GLDToken deployed to 0x5FbDB2315678afecb367f032d93F642f64180aa3 with an initialSupply 10000000000000000000000
```

---

#### Step 4: Write Unit Test

In the hardhat official tutorial, there is a detailed explanation of unit test at https://hardhat.org/tutorial/testing-contracts.html . We adapted the test script in it with several necessary changes.

Create file `GLDToken.test.ts`:

```ts
// We import Chai to use its asserting functions here.
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import { ethers } from "hardhat";

describe("GLDToken", 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.

    const initialSupply = 10000;

    async function deployGLDTokenFixture() {
  
      // Contracts are deployed using the first signer/account by default
      const [owner, otherAccount] = await ethers.getSigners();
  
      const GLDToken = await ethers.getContractFactory("GLDToken");
      const token = await GLDToken.deploy(initialSupply);
  
      return { token, owner, otherAccount };
    }
  
    describe("Deployment", function () {
      it("Should assign the total supply of tokens to the owner", async function () {
        const { token, owner } = await loadFixture(deployGLDTokenFixture);
        const total = await token.totalSupply();
        expect(total).to.equal(await token.balanceOf(owner.address));
      });

    });

    describe("Transaction", function () {

        it("Should transfer tokens between accounts", async function () {
            const { token, owner, otherAccount } = await loadFixture(deployGLDTokenFixture);

            const ownerBalance = await token.balanceOf(owner.address);
    
            await token.transfer(otherAccount.address, 50);
            const addr1Balance = await token.balanceOf(otherAccount.address);
            expect(addr1Balance).to.equal(50);

            const ownerNewBalance = await token.balanceOf(owner.address);
            expect(ownerNewBalance).to.equal(ownerBalance.sub(50));
        });
    
        it("Should fail if sender doesn’t have enough tokens", async function () {
            const { token, owner, otherAccount } = await loadFixture(deployGLDTokenFixture);
    
            // Transfer 10001 GLD tokens from owner to otherAccount
            await expect(
             token.transfer(otherAccount.address, ethers.utils.parseEther('10001'))
            ).to.be.revertedWith("ERC20: transfer amount exceeds balance");
        });        

      });
  
});

```

Run unit test:

```bash
yarn hardhat test  test/GLDToken.test.ts
```

Output:

```
  GLDToken
    Deployment
      ✔ Should assign the total supply of tokens to the owner (597ms)
    Transaction
      ✔ Should transfer tokens between accounts
      ✔ Should fail if sender doesn’t have enough tokens

  3 passing (630ms)
```

---

#### Step 5: Deploy smart contract to localhost and interact with it

By compiling, testing and deploying in in-process blockchain, we now have a smart contract which works correctly. We would like to deploy it to stand-alone localhost blockchain and interact with it interactively from Hardhat console.

Open another terminal and run in the project directory:

```bash
yarn hardhat node
```

In your working terminal, deploy the contract to localhost by running:

```bash
yarn hardhat run scripts/deploy_GLDToken.ts --network localhost 
//Output:
// GLDToken deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
```

Please note that you need to set localhost network in `hardhat.config.ts`.

Run hardhat console connecting to localhost:

```bash
yarn hardhat console --network localhost
```

Interact with GLDToken smart contract instance in hardhat console as follows. You need address and ABI to interact with a contract. The ABI(Application Binary Interface) is retrieved by Hardhat-ethers plugin's  `ethers.getContractAt("GLDToken", address)` using the contract name here.

Get a smart contract instance and retrieve read-only data:

```js
fromWei = ethers.utils.formatEther;
toWei = ethers.utils.parseEther;

const address = '0x5FbDB2315678afecb367f032d93F642f64180aa3';
const token = await ethers.getContractAt("GLDToken", address);

const accounts = await hre.ethers.getSigners();
owner = accounts[0].address;
toAddress = accounts[1].address;

await token.symbol()
//'GLD'

totalSupply = await token.totalSupply();
fromWei(totalSupply)
//10000.0
```

Some explanation on how to get contract instance: 

- We can get contract instance using Ethers.js directly([docs](https://docs.ethers.io/v5/api/contract/contract/#Contract--creating)): `new ethers.Contract( address , abi , signerOrProvider )`. By this way, we need to get abi from the compile output (in `artifacts` directory).

- We can use `hardhat-ethers` plugin's helper function `ethers.getContractAt("GLDToken", address)` ([docs](https://hardhat.org/hardhat-runner/plugins/nomiclabs-hardhat-ethers#helpers)) as we do in the above code snippet.

Once we get an instance of the ERC20 contract, we can call its functions ([ERC20 docs by OpenZeppelin](https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#ERC20)). 

You can also transfer token from your current address to another address by calling `transfer(recipient, amount)`. This is a state-changing function of the ERC20 contract.

``` ts
await token.transfer(toAddress, toWei('100'))

ownerBalance = await token.balanceOf(owner);
fromWei(ownerBalance);
//'9900.0'

toBalance = await token.balanceOf(toAddress);
fromWei(toBalance)
//'100.0'
```

---

### Note for playing with Public testnet

1. Create an [Alchemy](alchemy.com) Account and get API key
2. Get some test ETH for Sepolia testnet from free faucet: [Sepolia faucet](https://sepoliafaucet.com/)
3. Configure the network part in `hardhat.config.ts` for Sepolia testnet
4. Deploy the GLDToken ERC20 contract to Sepolia testnet
5. Play with your GLDToken contract using Hardhat console. 

Btw, Alchemy also provides some handy APIs for standard ERC20. For example, there is `alchemy_getTokenBalances` [link](https://docs.alchemy.com/docs/how-to-get-token-balance-for-an-address) to get token balance of address. 

---

Similarly, we can write an ERC721 token smart contract using OpenZeppelin. And in the next section, we will write a loot-like ERC721 NFT with on-chain SVG image.

---

If you feel this tutorial helpful and would like to know more, follow me Twitter: @fjun99 . DM is open.