Web3 Tutorial: build DApp with Hardhat, React and Ethers.js
Tags: blockchain, dapp, ethersjs, web3
Published: 2022-02-24 10:23:25

In this tutorial, we will build DAPP using Hardhat, React and Ethers.js which works with user-controlled wallets like MetaMask.

DAPP is usually composed of three parts:

- Smart contract deployed on chain
- Webapp (user interface) built with Node.js, React and Next.js
- Wallet (user-controlled in browser / mobile wallet App)

We use `Ethers.js` to connect these parts.

![DApp explained](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wa0zgy1ehy1hmpmc347q.png)
 

In the user interface of a DApp (webapp), wallets such as MetaMask gives developers a provider of Ethereum which we can use in `Ethers.js` to interact with blockchain. (To be specific, what wallet provide is a "connector", Ethers.js create "provider" and/or "signer" for us to use.)

We may have known the usage of MetaMask as a user, and we will learn how to use MetaMask and the `window.ethereum` it injects into the browser as a developer ([MetaMask developer docs](https://docs.metamask.io/guide/ethereum-provider.html#using-the-provider)). 

You can find the code repos for this tutorial:
Hardhat project: https://github.com/fjun99/chain-tutorial-hardhat-starter
Webapp project: https://github.com/fjun99/web3app-tutorial-using-ethers

You may find the very first how-to guide for DApp developers is useful for your web3 journey: [Tutorial : build your first DAPP with Remix and Etherscan(by Fangjun)](https://dev.to/yakult/tutorial-build-your-first-dapp-with-remix-and-etherscan-52kf).

**Special thanks**: When preparing the webapp codebase, I learned a lot for Wesley's [Proof-of-Competence, POC](https://github.com/wslyvh/proof-of-competence) project. We also use Chakra UI just as his project. You may also find the the web page is almost the same as POC. 


---

### Prerequisite Knowledge and tools

You need basic knowledge and tools before we start.

Knowledge:
- Blockchain
- Ethereum
- Wallet
- Solidity
- ERC20 & ERC721
- Ethers.js

Tools:
- MetaMask (Wallet browser extension)
- Node.js, yarn, TypeScript
- OpenZeppelin (Solidity library)
- Etherscan block explorer

Let's start to build a DApp.

---

![TASK #1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ggs0sbls0x1m2rpp4gd.png)
 

## Task 1: setup development environment

To build a DApp, we are doing two kinds of jobs: 

- build smart contract using Hardhat and Solidity
- build web app using Node.js, React and Next.js

We will organize our directory in two sub-directory `chain` and `webapp`.

```
- hhproject
  - chain (working dir for hardhat)
    - contracts
    - test
    - scripts
  - webapp (working dir for NextJS app)
    - src
      - pages
      - components  
```


### Task 1.1 Install Hardhat and init a Hardhat project

Install Hardhat which is an Ethereum development environment. 

To use hardhat, you need to have `node.js` and `yarn` in your computer.

- STEP 1: make a directory and install Hardhat in it
```
mkdir hhproject && cd hhproject
mkdir chain && cd chain
yarn init -y
```

Install Hardhat:
```
yarn add hardhat
```

- STEP 2: create a sample Hardhat project
```
yarn hardhat
//choose: Create an advanced sample project that uses TypeScript
```

A hardhat project is created with a sample smart contract `Greeter.sol` we will use in Task 3.

- STEP 3: run Hardhat Network (local testnet)
```
yarn hardhat node
```

A local testnet will is running (**chainId: 31337**):

> Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/

It provides 20 accounts each has `10000.0 test ETH`: 

``` 
Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)
Account #1: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8
...
```

Please note that there are two modes of Hardhat Network local testnet: in-process and stand-alone. We run a stand-alone testnet with command line `yarn hardhat node`. When running command line like `yarn hardhat compile` without network parameters(`--network localhost`), we run an in-process testnet.

### Task 1.2: Development Circle in Hardhat

We will go through the development circle of smart contract in Hardhat development environment. 

There are sample smart contract, test scripts and deploy scripts in the project initiated by Hardhat.

```
├── contracts
│   └── Greeter.sol
├── scripts
│   └── deploy.ts
├── test
│   └── index.ts
├── hardhat.config.ts
```

I would like to change the file names of test and deployment script.
```
- contracts
  - Greeter.sol
- test
  - Greeter.test.ts (<-index.ts)
- scripts
  - deploy_greeter.ts (<-deploy.ts)
```

STEP 1: Run command to show accounts:

```
yarn hardhat accounts
```

This is the sample hardhat task added in the `hardhat.config.ts`.

STEP 2: Compile smart contract

```
yarn hardhat compile
```

STEP 3: Run unit test
```
yarn hardhat test
```

STEP 4: Try to deploy to in-process testnet
```
yarn hardhat run ./scripts/deploy_greeter.ts
```

In the next two steps, we will run a stand-alone Hardhat Network and deploy smart contracts to it.

STEP 5: Run a stand-alone local testnet

In another terminal, run:
```
yarn hardhat node
//Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
```

STEP 6: Deploy to stand-alone local testnet
```
yarn hardhat run ./scripts/deploy.ts --network localhost
//Greeter deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
```

If you run the deployment several times, you will find that the contract instances are deployed to different addresses.

### Task 1.3: Switch MetaMask to Local testnet

Please make sure Hardhat Network local testnet is still running. (You can run it by command `yarn hardhat node`.)

- STEP 1: In MataMask browser extension, click the network selector on the top bar. Switch the network from `mainnet` to `localhost 8545`.

- STEP 2: Click the Account icon on the topbar and go to "Settings / Network / ". Choose "localhost 8445".
 
Note: make sure Chain ID is **31337**. It may be "1337" by default in MetaMask.

### Task 1.4: Create webapp with Next.js and Chakra UI

We will create a webapp using `Node.js`, `React`, `Next.js` and `Chakra UI` frameworks. (You may choose any other UI frameworks you like such as `Material UI`, `Ant Design`, etc. You may would like to choose front-end framework `Vue` instead of `Next.js`)

- STEP 1: create Next.js project `webapp`

In `hhproject/` directory, run:

```
yarn create next-app webapp --typescript
//will make a sub-dir webapp and create an empty Next.js project in it

cd webapp
```

- STEP 2: change some defaults and run webapp

We will use `src` as our app directory instead of `pages` (more about `src` and `pages` [ in Next.js docs](https://nextjs.org/docs/advanced-features/src-directory)):

```
mkdir src
mv pages src/pages
mv styles src/styles

vim tsconfig.json
//in "compilerOptions" add:
//        "baseUrl": "./src"
```

Run Next.js app and view it in your browser:
```
yarn dev
//ready - started server on 0.0.0.0:3000, url: http://localhost:3000
```

Browse `http://localhost:3000`

- STEP 3: install Chakra UI

Install Chakra UI ([docs](https://chakra-ui.com/docs/getting-started)) by running: 
```
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
```

We will edit the next.js app in the next sub-task to make it suitable for our project.

### Task 1.5: Edit webapp - header, layout, _app.tsx, index.tsx

- STEP 1: add a header component

```
mkdir src/components
touch src/components/header.tsx
```

Edit `header.tsx` to be:
``` js
//src/components/header.tsx
import NextLink from "next/link"
import { Flex, Button, useColorModeValue, Spacer, Heading, LinkBox, LinkOverlay } from '@chakra-ui/react'

const siteTitle="FirstDAPP"
export default function Header() {

  return (
    <Flex as='header' bg={useColorModeValue('gray.100', 'gray.900')} p={4} alignItems='center'>
      <LinkBox>
        <NextLink href={'/'} passHref>
          <LinkOverlay>
            <Heading size="md">{siteTitle}</Heading>
          </LinkOverlay>
        </NextLink>
      </LinkBox>      
      <Spacer />
      <Button >Button for Account </Button>
    </Flex>
  )
}
```

- STEP 2: add Next.js layout

Add layout  ([docs](https://nextjs.org/docs/basic-features/layouts))
```
touch src/components/layout.tsx
```

Edit `layout.tsx` to be:
``` js
// src/components/layout.tsx
import React, { ReactNode } from 'react'
import { Text, Center, Container, useColorModeValue } from '@chakra-ui/react'
import Header from './header'

type Props = {
  children: ReactNode
}

export function Layout(props: Props) {
  return (
    <div>
      <Header />
      <Container maxW="container.md" py='8'>
        {props.children}
      </Container>
      <Center as="footer" bg={useColorModeValue('gray.100', 'gray.700')} p={6}>
          <Text fontSize="md">first dapp by W3BCD - 2022</Text>
      </Center>
    </div>
  )
}
```

- STEP 3: Add Chakra UI Provider to "_app.tsx" as well as layout

Edit `_app.tsx`

``` js
// src/pages/_app.tsx
import { ChakraProvider } from '@chakra-ui/react'
import type { AppProps } from 'next/app'
import { Layout } from 'components/layout'

function MyApp({ Component, pageProps }: AppProps) {
  return (
      <ChakraProvider>
        <Layout>
        <Component {...pageProps} />
        </Layout>
      </ChakraProvider>
  )
}

export default MyApp
```

- STEP 4: Edit "index.tsx"
``` js
// src/pages/index.tsx
import type { NextPage } from 'next'
import Head from 'next/head'
import NextLink from "next/link"
import { VStack, Heading, Box, LinkOverlay, LinkBox} from "@chakra-ui/layout"
import { Text, Button } from '@chakra-ui/react'

const Home: NextPage = () => {
  return (
    <>
      <Head>
        <title>My DAPP</title>
      </Head>

      <Heading as="h3"  my={4}>Explore Web3</Heading>          
      <VStack>
        <Box  my={4} p={4} w='100%' borderWidth="1px" borderRadius="lg">
          <Heading my={4}  fontSize='xl'>Task 1</Heading>
          <Text>local chain with hardhat</Text>
        </Box>

        <Box  my={4} p={4} w='100%' borderWidth="1px" borderRadius="lg">
          <Heading my={4}  fontSize='xl'>Task 2</Heading>
          <Text>DAPP with React/NextJS/Chakra</Text>
        </Box>

        <LinkBox  my={4} p={4} w='100%' borderWidth="1px" borderRadius="lg">
          <NextLink href="https://github.com/NoahZinsmeister/web3-react/tree/v6" passHref>
          <LinkOverlay>
            <Heading my={4} fontSize='xl'>Task 3 with link</Heading>
            <Text>Read docs of Web3-React V6</Text>
          </LinkOverlay>
          </NextLink>
        </LinkBox>
      </VStack>
    </>
  )
}

export default Home
```

You may also want to add "_documents.tsx" ([docs](https://nextjs.org/docs/advanced-features/custom-document)) to customize pages in your Next.js app.

You may want to delete unneeded files in this project such as `src/styles`.

- STEP 5: Run webapp

```
yarn dev
```

The page at http://localhost:3000/ will look like: 

![DAPP webapp](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vfqw3iuq5ch6ra6zervh.png)
 

---

You can download the code from github [scaffold repo](https://github.com/fjun99/web3app-tutorial-starter-scaffold).

In your 'hhproject/' directory: 
```
git clone git@github.com:fjun99/web3app-tutorial-using-ethers.git webapp
cd webapp
yarn install
yarn dev
```

---

![TASK #2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y2lbemvzpiijvcs8r2fc.png)
 

## Task 2: Connect DApp to blockchain through MetaMask

In this task, we will create a DAPP which can connect to blockchain(local testnet) through MetaMask.

We will interact with the blockchain using Javascript API library `Ethers.js`.

### Task 2.1: install `Ethers.js`

In `webapp/` directory, add `Ethers.js`
```
yarn add ethers
```

### Task 2.2: connect to MetaMask wallet

![display ETH balance](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wcibuc05mywq1gvaog4s.png)
 

We will add a button on `index.tsx`: 

- when not connected, the button text is "Connect Wallet". Click to link blockchain through MetaMask.

- when connected, button text is connected account address. User can click to disconnect.

We will get ETH balance of the current account and display on the page as well as blockchain network info.

There are Ethers.js docs about connect MetaMask ([link](https://docs.ethers.io/v5/getting-started/#getting-started--connecting)).

I wrote a slide to explain the relationship between `connector`, `provider`, `signer` and wallet in `Ethers.js`!

![connector, provider, signer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jjlkxpctk4gexod0c29g.png)
 

We will use [react hook](https://reactjs.org/docs/hooks-intro.html) feature `useState` and `useEffect` 

Related code snippet in `src/pages/index.tsx`
``` js
// src/pages/index.tsx
...
import { useState, useEffect} from 'react'
import {ethers} from "ethers"

declare let window:any

const Home: NextPage = () => {
  const [balance, setBalance] = useState<string | undefined>()
  const [currentAccount, setCurrentAccount] = useState<string | undefined>()
  const [chainId, setChainId] = useState<number | undefined>()
  const [chainname, setChainName] = useState<string | undefined>()

  useEffect(() => {
    if(!currentAccount || !ethers.utils.isAddress(currentAccount)) return
    //client side code
    if(!window.ethereum) return
    const provider = new ethers.providers.Web3Provider(window.ethereum)
    provider.getBalance(currentAccount).then((result)=>{
      setBalance(ethers.utils.formatEther(result))
    })
    provider.getNetwork().then((result)=>{
      setChainId(result.chainId)
      setChainName(result.name)
    })

  },[currentAccount])

  const onClickConnect = () => {
    //client side code
    if(!window.ethereum) {
      console.log("please install MetaMask")
      return
    }
    /*
    //change from window.ethereum.enable() which is deprecated
    //see docs: https://docs.metamask.io/guide/ethereum-provider.html#legacy-methods
    window.ethereum.request({ method: 'eth_requestAccounts' })
    .then((accounts:any)=>{
      if(accounts.length>0) setCurrentAccount(accounts[0])
    })
    .catch('error',console.error)
    */

    //we can do it using ethers.js
    const provider = new ethers.providers.Web3Provider(window.ethereum)

    // MetaMask requires requesting permission to connect users accounts
    provider.send("eth_requestAccounts", [])
    .then((accounts)=>{
      if(accounts.length>0) setCurrentAccount(accounts[0])
    })
    .catch((e)=>console.log(e))
  }
  
  const onClickDisconnect = () => {
    console.log("onClickDisConnect")
    setBalance(undefined)
    setCurrentAccount(undefined)
  }

  return (
    <>
      <Head>
        <title>My DAPP</title>
      </Head>

      <Heading as="h3"  my={4}>Explore Web3</Heading>          
      <VStack>
        <Box w='100%' my={4}>
        {currentAccount  
          ? <Button type="button" w='100%' onClick={onClickDisconnect}>
                Account:{currentAccount}
            </Button>
          : <Button type="button" w='100%' onClick={onClickConnect}>
                  Connect MetaMask
              </Button>
        }
        </Box>
        {currentAccount  
          ?<Box  mb={0} p={4} w='100%' borderWidth="1px" borderRadius="lg">
          <Heading my={4}  fontSize='xl'>Account info</Heading>
          <Text>ETH Balance of current account: {balance}</Text>
          <Text>Chain Info: ChainId {chainId} name {chainname}</Text>
        </Box>
        :<></>
        }
...
      </VStack>
    </>
  )
}

export default Home
```

Some explanations: 

- We add two UI components: one for the connect button, one for displaying account and chain information.

- When "Connect MetaMask" button is clicked, we do:
  - get `Web3Provider` through connector(`window.ethereum`) which MetaMask injected to the page.
  - call `eth_requestAccounts`, which will ask MetaMask to confirm sharing account information. Users can confirm or reject the request in MetaMask popup. 
  - set the returned account to `currentAccount`.

- When disconnect is called, we reset currentAccount and balance.

- Every time the currentAccount is changed, the side effect (useEffect) is called. We will query: 
  - ETH balance of the current account by calling `getBalance`.
  - network information by calling `getNetwork()`.

Please note that:

- Disconnect in page will not change MetaMask connection and permissions to this page. Open MetaMask extension, you will see that your wallet is still connected to this page. Next time you click `Connect MetaMask` button again, the MetaMask will not popup for confirmation (as your confirmation is still active). You need to disconnect wallet and page from MetaMask.

- We do not write codes to display changes when the user switches network in MetaMask. 

- We do not store the state of this page. So when the page is refreshed, the connection is reset.

---

![TASK #3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9esnbqa9hjvbwrtrf2jw.png)
 

## Task 3: Build ERC20 smart contract using OpenZeppelin

In Task 3, we will build ERC20 smart contract using OpenZeppelin library ([ERC20 docs)](https://docs.openzeppelin.com/contracts/4.x/api/token/erc20).

### Task 3.1: Write an ERC20 smart contract - ClassToken

Add `OpenZeppelin/contract`:

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

Go to `chain/` directory and add `contracts/ClassToken.sol`:

``` solidity
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

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

contract ClassToken is ERC20 {
        constructor(uint256 initialSupply) 
          ERC20("ClassToken", "CLT") 
        {
                _mint(msg.sender, initialSupply);
        }
}

```

### Task 3.2 Compile smart contract

```
yarn hardhat compile
//Solidity compilation should succeed
```

### Task 3.3 Add unit test script

Add unit test script `test/ClassToken.test.ts`

``` js
import { expect } from "chai";
import { ethers } from "hardhat";

describe("ClassToken", function () {
  it("Should have the correct initial supply", async function () {
    const initialSupply = ethers.utils.parseEther('10000.0')
    const ClassToken = await ethers.getContractFactory("ClassToken");
    const token = await ClassToken.deploy(initialSupply);
    await token.deployed();

    expect(await token.totalSupply()).to.equal(initialSupply);
  });
});
```

Run unit test:

```
yarn hardhat test
//  ClassToken
//    ✓ Should have the correct initial supply (392ms)
//  1 passing (401ms)
```


### Task 3.4 Add deploy script

Add deploy script `scripts/deploy_classtoken.ts`

``` js
import { ethers } from "hardhat";

async function main() {

  const initialSupply = ethers.utils.parseEther('10000.0')
  const ClassToken = await ethers.getContractFactory("ClassToken");
  const token = await ClassToken.deploy(initialSupply);
  await token.deployed();

  console.log("ClassToken deployed to:", token.address);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
```

We deploy ClassToken with initialSupply `10000.0 CLT` sent to deployer(`msg.sender`).

Try to run contract deployment to in-process Hardhat Network local testnet (in-process mode):

```
yarn hardhat run  scripts/deploy_classtoken.ts
```

### Task 3.5 Run stand-alone testnet to deploy smart contract to it

In another terminal, run in `chain/` directory:
```
yarn hardhat node
```

In the current terminal, run hardhat tasks connecting to localhost `--network localhost`:
```
yarn hardhat run scripts/deploy_classtoken.ts --network localhost
//ClassToken deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
```

### Task 3.6 Interact with ClassToken in hardhat console

Run hardhat console connecting to stand-alone local testnet:
```
yarn hardhat console  --network localhost
```

Interact with `ClassToken` in console:
``` js
formatEther = ethers.utils.formatEther;
address = '0x5FbDB2315678afecb367f032d93F642f64180aa3';
token = await ethers.getContractAt("ClassToken", address);

totalSupply = await token.totalSupply();
formatEther(totalSupply)
//'10000.0'
```

`ethers.getContractAt()` is a helper function provided by Hardhat plugin `hardhat-ethers`, [docs link](https://hardhat.org/plugins/nomiclabs-hardhat-ethers.html#helpers).

### Task 3.7: Add token to MetaMask

Add token to MetaMask with Address:`0x5FbDB2315678afecb367f032d93F642f64180aa3`. (**Please use the deployed contract address you get.**)

We can see that Account#0(`0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`) has `10000.0 CLT`.

You can also download the hardhat sample project from [github repo](https://github.com/fjun99/chain-tutorial-hardhat-starter).

```
git clone git@github.com:fjun99/chain-tutorial-hardhat-starter.git chain
yarn install
// then, you can run stand-alone testnet and 
// go through the compile-test-deploy circle 
```

--- 

![TASK #4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sr1g3jdtst67wihabyvd.png)
 

## Task 4: Read contract data - interact with smart contract in webapp

In task 4 and task 5, we will continue to build our webapp.

We will allow users to interact with the newly deployed ERC20 Token smart contract - `ClassToken(CLT)`.

![DApp ERC20](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mv51b1i0u7e96luzbao1.png)
 


### Task 4.1: Add empty `ReadERC20` component to read ClassToken

In webapp directory, add an empty component `components/ReadERC20.tsx`

``` js
import React, { useEffect,useState } from 'react'
import { Text} from '@chakra-ui/react'
interface Props {
    addressContract: string,
    currentAccount: string | undefined
}

export default function ReadERC20(props:Props){
  const addressContract = props.addressContract
  const currentAccount = props.currentAccount


return (
    <div>
        <Text >ERC20 Contract: {addressContract}</Text>
        <Text>token totalSupply:</Text>
        <Text my={4}>ClassToken in current account:</Text>
    </div>
  )
}

```

Import and add this component to `index.tsx`:

``` js
        <Box  mb={0} p={4} w='100%' borderWidth="1px" borderRadius="lg">
          <Heading my={4}  fontSize='xl'>Read ClassToken Info</Heading>
          <ReadERC20 
            addressContract='0x5FbDB2315678afecb367f032d93F642f64180aa3'
            currentAccount={currentAccount}
          />
        </Box>

```

In the following sub-tasks, we will add functionality of `ReadERC20` Component step by step.

### Task 4.2: Prepare smart contract ABI

To interact with a smart contract in Javascript, we need its [ABI](https://docs.soliditylang.org/en/v0.8.11/abi-spec.html):

> Contract **Application Binary Interface** (ABI) is the standard way to interact with contracts in the Ethereum ecosystem. Data is encoded according to its type. 

ERC20 smart contract is a standard and we will use a file with human-readable ABI instead of the compiled artifacts output in our Hardhat project. What we add is [human-readable ABI](https://docs.ethers.io/v5/api/utils/abi/formats/#abi-formats--human-readable-abi).

Add a directory `src/abi` and create a file `src/abi/ERC20ABI.tsx`

``` js
export const ERC20ABI = [
    // Read-Only Functions
    "function balanceOf(address owner) view returns (uint256)",
    "function totalSupply() view returns (uint256)",
    "function decimals() view returns (uint8)",
    "function symbol() view returns (string)",
    // Authenticated Functions
    "function transfer(address to, uint amount) returns (bool)",
    // Events
    "event Transfer(address indexed from, address indexed to, uint amount)"
];
```

### Task 4.3: Query smart contract info when component loaded

We use React hook `useEffect` to query smart contract info when the component is loaded (mounted).

Edit `ReadERC20.tsx`
``` js
// src/components/ReadERC20.tsx
import React, {useEffect, useState } from 'react';
import {Text} from '@chakra-ui/react'
import {ERC20ABI as abi} from 'abi/ERC20ABI'
import {ethers} from 'ethers'

interface Props {
    addressContract: string,
    currentAccount: string | undefined
}

declare let window: any;

export default function ReadERC20(props:Props){
  const addressContract = props.addressContract
  const currentAccount = props.currentAccount
  const [totalSupply,setTotalSupply]=useState<string>()
  const [symbol,setSymbol]= useState<string>("")

  useEffect( () => {
    if(!window.ethereum) return

    const provider = new ethers.providers.Web3Provider(window.ethereum)
    const erc20 = new ethers.Contract(addressContract, abi, provider);
    erc20.symbol().then((result:string)=>{
        setSymbol(result)
    }).catch('error', console.error)

    erc20.totalSupply().then((result:string)=>{
        setTotalSupply(ethers.utils.formatEther(result))
    }).catch('error', console.error);
    //called only once
  },[])  

  return (
    <div>
        <Text><b>ERC20 Contract</b>: {addressContract}</Text>
        <Text><b>ClassToken totalSupply</b>:{totalSupply} {symbol}</Text>
        <Text my={4}><b>ClassToken in current account</b>:</Text>
    </div>
  )
}

```

Explanations:

- Hook `useEffect(()=>{},[])` will only be called once.

- Create a [Web3Provider](https://docs.ethers.io/v5/api/providers/other/#Web3Provider) with `window.ethereum`, ethereum connector injected into page by MetaMask.

- Create a contract instance with `addressContract`, `abi`, `provider` in `Ethers.js`.

- Call read-only functions `symbol()`, `totalSupply()` and set results to react state variable which can be displayed on page.

### Task 4.3: Query CLT balance of current account when account changes

Edit `ReadERC20.tsx`

``` js
// src/components/ReadERC20.tsx
  const [balance, SetBalance] =useState<number|undefined>(undefined)
...
  //call when currentAccount change
  useEffect(()=>{
    if(!window.ethereum) return
    if(!currentAccount) return

    queryTokenBalance(window)
  },[currentAccount])
  
  async function queryTokenBalance(window:any){
    const provider = new ethers.providers.Web3Provider(window.ethereum)
    const erc20 = new ethers.Contract(addressContract, abi, provider);

    erc20.balanceOf(currentAccount)
    .then((result:string)=>{
        SetBalance(Number(ethers.utils.formatEther(result)))
    })
    .catch('error', console.error)
  }  
...
  return (
    <div>
        <Text><b>ERC20 Contract</b>: {addressContract}</Text>
        <Text><b>ClassToken totalSupply</b>:{totalSupply} {symbol}</Text>
        <Text my={4}><b>ClassToken in current account</b>: {balance} {symbol}</Text>
    </div>
  )
```

Explanation: 

- When `currentAccount` changes, the side effect hook `useEffect(()=>{},[currentAccount]` is called. We call `balanceOf(address)` to get the balance.

- When we refresh the page, there is no current account and no balance is displayed. After we connect wallet, the balance is queried and displayed on the page. 

There are still more jobs to be done: 

- When MetaMask switch account, our web app doesn't know and will not change the display in the page. We need to listen event of MetaMask account change.

- When the balance of the current account changes, our web app will not update since your current account has not been changed. 

You can send CLT to others using MetaMask and you will find that we need to update the account balance of CLT on the page. We will do it in task 6. In task 5, we will build transfer components for users first.

---

![TASK #5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nk5f5j37y9vxwaemb15r.png)
 

## Task 5: Write/Transfer: Interact with smart contract in web App

### Task 5.1: Add empty `TransferERC20` component 

``` js
// src/component/TransferERC20.tsx
import React, { useEffect,useState } from 'react';
import { Text, Button, Input , NumberInput,  NumberInputField,  FormControl,  FormLabel } from '@chakra-ui/react'

interface Props {
    addressContract: string,
    currentAccount: string | undefined
}

export default function ReadERC20(props:Props){
  const addressContract = props.addressContract
  const currentAccount = props.currentAccount
  const [amount,setAmount]=useState<string>('100')
  const [toAddress, setToAddress]=useState<string>("")

  async function transfer(event:React.FormEvent) {
    event.preventDefault()
    console.log("transfer clicked")
 }

  const handleChange = (value:string) => setAmount(value)

  return (
    <form onSubmit={transfer}>
    <FormControl>
    <FormLabel htmlFor='amount'>Amount: </FormLabel>
      <NumberInput defaultValue={amount} min={10} max={1000} onChange={handleChange}>
        <NumberInputField />
      </NumberInput>
      <FormLabel htmlFor='toaddress'>To address: </FormLabel>
      <Input id="toaddress" type="text" required  onChange={(e) => setToAddress(e.target.value)} my={3}/>
      <Button type="submit" isDisabled={!currentAccount}>Transfer</Button>
    </FormControl>
    </form>
  )
}

```

Import and add this component to index.tsx:

``` js
        <Box  mb={0} p={4} w='100%' borderWidth="1px" borderRadius="lg">
          <Heading my={4}  fontSize='xl'>Transfer Classtoken</Heading>
          <TransferERC20 
            addressContract='0x5FbDB2315678afecb367f032d93F642f64180aa3'
            currentAccount={currentAccount}
          />
        </Box>
```

### Task 5.2: Implement transfer() function

Implement the transfer function in `TransferERC20.tsx`:

``` js
// src/component/TransferERC20.tsx
import React, { useState } from 'react'
import {Button, Input , NumberInput,  NumberInputField,  FormControl,  FormLabel } from '@chakra-ui/react'
import {ethers} from 'ethers'
import {parseEther } from 'ethers/lib/utils'
import {ERC20ABI as abi} from 'abi/ERC20ABI'
import { Contract } from "ethers"
import { TransactionResponse,TransactionReceipt } from "@ethersproject/abstract-provider"

interface Props {
    addressContract: string,
    currentAccount: string | undefined
}

declare let window: any;

export default function TransferERC20(props:Props){
  const addressContract = props.addressContract
  const currentAccount = props.currentAccount
  const [amount,setAmount]=useState<string>('100')
  const [toAddress, setToAddress]=useState<string>("")

  async function transfer(event:React.FormEvent) {
    event.preventDefault()
    if(!window.ethereum) return    
    const provider = new ethers.providers.Web3Provider(window.ethereum)
    const signer = provider.getSigner()
    const erc20:Contract = new ethers.Contract(addressContract, abi, signer)
    
    erc20.transfer(toAddress,parseEther(amount))
      .then((tr: TransactionResponse) => {
        console.log(`TransactionResponse TX hash: ${tr.hash}`)
        tr.wait().then((receipt:TransactionReceipt)=>{console.log("transfer receipt",receipt)})
      })
      .catch((e:Error)=>console.log(e))

  }

  const handleChange = (value:string) => setAmount(value)

  return (
    <form onSubmit={transfer}>
    <FormControl>
    <FormLabel htmlFor='amount'>Amount: </FormLabel>
      <NumberInput defaultValue={amount} min={10} max={1000} onChange={handleChange}>
        <NumberInputField />
      </NumberInput>
      <FormLabel htmlFor='toaddress'>To address: </FormLabel>
      <Input id="toaddress" type="text" required  onChange={(e) => setToAddress(e.target.value)} my={3}/>
      <Button type="submit" isDisabled={!currentAccount}>Transfer</Button>
    </FormControl>
    </form>
  )
}


```

Explanation:

- We call `transfer(address recipient, uint256 amount) → bool`, which is state-change function of ERC20 smart contract. 

As you can see, the balance of ClassToken is not changed after transferring. We will fix this in Task 6.

---

![TASK #6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9v8y5kw53xbzv40cjrkd.png)
 

## Task 6: Listen to Event: Interact with smart contract in web App

We can update CLT balance with the design of smart contract events. For ERC20 token smart contract, when a transfer is confirmed on-chain, an Event `Transfer(address from, address to, uint256 value)` ([docs](https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#IERC20-Transfer-address-address-uint256-)) is emitted. 

We can listen to this event in Node.js webapp and update the page display.

### Task 6.1: Understanding Smart contract events

Events explained in plain English. When we call a state-change function of a smart contract, there are three steps: 

- STEP 1: Off-chain call. We call a state-change function of a smart contract using JavaScript API (ethers.js) off-chain. 

- STEP 2: On-chain confirmation. State-change transactions need to be confirmed by miners using consensus algorithms in several blocks on-chain. So we can't get the result immediately.

- STEP 3: Emit events. Once the transaction is confirmed, an event is emitted. You can listen to events to get the results off-chain.

![events explained](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gcb06u6il2z6ace5fl7y.png)
 

### Task 6.2: Add event listener when current account change

Edit `ReadERC20.tsx`: 
``` js
  //call when currentAccount change
  useEffect(()=>{
    if(!window.ethereum) return
    if(!currentAccount) return

    queryTokenBalance(window)

    const provider = new ethers.providers.Web3Provider(window.ethereum)
    const erc20 = new ethers.Contract(addressContract, abi, provider)

    // listen for changes on an Ethereum address
    console.log(`listening for Transfer...`)

    const fromMe = erc20.filters.Transfer(currentAccount, null)
    provider.on(fromMe, (from, to, amount, event) => {
        console.log('Transfer|sent', { from, to, amount, event })
        queryTokenBalance(window)
    })

    const toMe = erc20.filters.Transfer(null, currentAccount)
    provider.on(toMe, (from, to, amount, event) => {
        console.log('Transfer|received', { from, to, amount, event })
        queryTokenBalance(window)
    })

    // remove listener when the component is unmounted
    return () => {
        provider.removeAllListeners(toMe)
        provider.removeAllListeners(fromMe)
    }    
  }, [currentAccount])
```

This code snippet is adapted from [How to Fetch and Update Data From Ethereum with React and SWR](https://consensys.net/blog/developers/how-to-fetch-and-update-data-from-ethereum-with-react-and-swr/).

Explanation:

- When `currentAccount` change (useEffect), we add two listeners: one for events transfer from currentAccount, another for transfer to currentAccount.

- When listening to an event, query the token balance of currentAccount and update the page.

You can transfer token from the current account on the page or in MetaMask, and you will see that the page is updating upon events.

When completing Task 6, you have built a simple but functional DAPP which has smart contract and webapp. 

---

In summary, there are three parts of a DAPP:

- smart contract and blockchain
- web app (user interface) to get and set data with smart contract
- user-controlled wallet (MetaMask here) which works as an asset management tool and signer for users, as well as connector to blockchain.

In these tasks, you may also notice that we interact with smart contracts in 3 ways:

- **Read**, get data from smart contract
- **Write**, set data in smart contract
- **Listen**, listen to events emitted by smart contract.

We use `Ethers.js` directly to connect to blockchain in this tutorial. But Web3-react and other libraries can handle connections to Ethereum node in React while the `Ethers.js` is running in the underling. That's the topic of our next tutorial - build DApp with Web3-React.

---

A note: You may find the very first how-to guide for DApp developers is useful for your web3 journey: [Tutorial : build your first DAPP with Remix and Etherscan(by Fangjun)](https://dev.to/yakult/tutorial-build-your-first-dapp-with-remix-and-etherscan-52kf).

---

If you find this tutorial helpful, follow me at Twitter [@fjun99](https://twitter.com/fjun99)