Web3 Tutorial: build DApp with Web3-React and SWR
Tags: blockchain, web3, dapp, react
Published: 2022-02-24 14:18:00

In "[Tutorial: Build DAPP with hardhat, React and Ethers.js](https://dev.to/yakult/a-tutorial-build-dapp-with-hardhat-react-and-ethersjs-1gmi)", we connect to and interact with the blockchain using `Ethers.js` directly. It is ok, but there are tedious processes needed to be done by ourselves. 

We would rather use handy frameworks to help us in three aspects:

1. maintain context and connect with blockchain.

2. connect to different kinds of blockchain providers.

3. query blockchain more efficiently.

[Web3-React](https://github.com/NoahZinsmeister/web3-react/tree/v6), a connecting framework for React and Ethereum, can help us with job 1 & 2. (We will focus on job 1.) Web3-React is an open source framework developed by Uniswap engineering Lead Noah Zinsmeister. You can also try [WAGMI: React Hooks for Ethereum](https://wagmi.sh/).

SWR can help us to query blockchains efficiently. SWR (stale-while-revalidate) is a library of react hooks for data fetching. I learned how to use SWR with blockchain from Lorenzo Sicilia's tutorial [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/).

I am still trying to find an efficient way to deal with Event. The Graph (sub-graph) is one of the good choices. [The Graph Protocol](https://thegraph.com/en/) and sub-graph are widely used by DeFi applications. In Nader Dabit's tutorial "[The Complete Guide to Full Stack Web3 Development](https://dev.to/dabit3/the-complete-guide-to-full-stack-web3-development-4g74)", he gives us a clear guide on how to use sub-graph.

Special thanks to Lorenzo Sicilia and his tutorial. I adapted the SWR flow and some code snippets from him. 

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-tutrial-using-web3react

Let's start building our DApp using Web3-React.

---

![Task #1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8kbdczsudwvjvy1htprk.png)
 
## Task 1: Prepare webapp project and smart contract 

The first half of Task 1 is the same as the ones in "[Tutorial: build DApp with Hardhat, React and Ethers.js](https://dev.to/yakult/a-tutorial-build-dapp-with-hardhat-react-and-ethersjs-1gmi)". Please refer to that tutorial.

> ### Tutorial: build DApp with Hardhat, React and Ethers.js
> https://dev.to/yakult/a-tutorial-build-dapp-with-hardhat-react-and-ethersjs-1gmi
>
> ### Task 1: setup development environment
> #### Task 1.1 Install Hardhat and init a Hardhat project
> #### Task 1.2 Development Circle in Hardhat
> #### Task 1.3 MetaMask Switch Local testnet
> #### Task 1.4 Create webapp with Next.js and Chakra UI
> #### Task 1.5 Edit webapp - header, layout, _app.tsx, index.tsx


We choose to download the webapp scaffold code from [our github repo](https://github.com/fjun99/webapp-tutorial-scaffold).

First, we make a `hhproject/` directory for our project (`hhproject/chain/` for hardhat project, `hhproject/webapp/` for React/Node.js webapp): 

```
mkdir hhproject && cd hhproject
```

Project directory structure:

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

Download an empty webapp scaffold:
```
git clone https://github.com/fjun99/webapp-tutorial-scaffold.git webapp
cd webapp
yarn install
yarn dev
```

We also need to prepare an ERC20 token ClassToken for our webapp to interact with. This is the second half of Task 1.

This job can be done same as Task 3 of "Tutorial: build DApp with Hardhat, React and Ethers.js"

> ### Task 3: Build ERC20 smart contract using OpenZeppelin
> #### Task 3.1 Write ERC20 smart contract
> #### Task 3.2 Compile smart contract
> #### Task 3.3 Add unit test script
> #### Task 3.4 Add deploy script
> #### Task 3.5 Run stand-alone testnet again and deploy to it
> #### Task 3.6 Interact with ClassToken in hardhat console
> #### Task 3.7 Add token to MetaMask

Again, we choose to download the hardhat chain starter project from [github repo](https://github.com/fjun99/chain-tutorial-hardhat-starter).In your `hhproject/` directory: 

```
git clone git@github.com:fjun99/chain-tutorial-hardhat-starter.git chain
cd chain
yarn install
```

Let's run "compile, test, deploy" circle of smart contract development.

In another terminal, run command line in `hhproject/chain/` directory to start a stand-alone Hardhat Network (local testnet) :

```
yarn hardhat node
```

Then compile, test and deploy smart contract:
```
yarn hardhat compile
yarn hardhat test test/ClassToken.test.ts
yarn hardhat run scripts/deploy_classtoken.ts --network localhost
// ClassToken deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
// ✨  Done in 4.04s.
```

Now we have ClassToken deployed to local testnet: `0x5FbDB2315678afecb367f032d93F642f64180aa3`

--- 

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

## Task 2: Add Web3-React to our webapp - Connect button

### Task 2.1: Understanding Web3-React

![Web3-react explained](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zyxu90s949jel93g3rke.png)
 

From my point of view, Web3-React is a web3 blockchain **connecting framework** which provides three features we need:

- Web3ReactProvder, a react context we can access throughout our web app.

- useWeb3React, handy react hook to interact with blockchain.
 
- Connectors of several kinds of blockchain providers, such as MetaMask (browser extension), RPC connector(Alchemy and Infura), QR code connector(WalletConnect), Hardware connector (Ledger/Trezor).

Currently Web3-React has [stable V6](https://github.com/NoahZinsmeister/web3-react/tree/v6) and [beta V8](https://github.com/NoahZinsmeister/web3-react/tree/main). We will use V6 in our tutorial.

### Task 2.2: Install `Web3-React`, `Ethers.js` and add `Web3ReactProvder`

STEP 1: install dependencies

In the `webapp` directory, run:

```
yarn add @web3-react/core
yarn add @web3-react/injected-connector
yarn add ethers
yarn add swr
```

We will use `swr` later.

STEP 2: edit `pages/_app.tsx`:

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

function getLibrary(provider: any): Web3Provider {
  const library = new Web3Provider(provider)
  return library
}


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

export default MyApp
```

Explanations:

- We add a [react context provider](https://reactjs.org/docs/context.html#contextprovider) `Web3ReactProvider` in `_app.tsx`.

- Blockchain provider (library) is an Ethers.js `Web3Provider` which we can add connector and activate later using hooks.

### Task 2.3: Add an empty ConnectMetamask component

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

The relationship between connector, provider and signer in `Ethers.js` is illustrated in the graph.

In this sub-task we will add an empty ConnectMetamask component.

- STEP 1: Add `src/components/ConnectMetamask.tsx`:

``` js
import { useEffect } from 'react'

import { useWeb3React } from '@web3-react/core'
import { Web3Provider } from '@ethersproject/providers'
import { Box, Button, Text} from '@chakra-ui/react'
import { injected } from 'utils/connectors'
import { UserRejectedRequestError } from '@web3-react/injected-connector'
import { formatAddress } from 'utils/helpers'

const ConnectMetamask = () => {

    const { chainId, account, activate,deactivate, setError, active,library ,connector} = useWeb3React<Web3Provider>()

    const onClickConnect = () => {
      activate(injected,(error) => {
        if (error instanceof UserRejectedRequestError) {
          // ignore user rejected error
          console.log("user refused")
        } else {
          setError(error)
        }
      }, false)
    }
    
    const onClickDisconnect = () => {
        deactivate()
      }
      
    useEffect(() => {
      console.log(chainId, account, active,library,connector)
    })
  
    return (
        <div>
        {active && typeof account === 'string' ? (
          <Box>  
            <Button type="button" w='100%' onClick={onClickDisconnect}>
                Account: {formatAddress(account,4)}
            </Button>
            <Text fontSize="sm" w='100%' my='2' align='center'>ChainID: {chainId} connected</Text>
          </Box>
        ) : (
          <Box>
            <Button type="button" w='100%' onClick={onClickConnect}>
                Connect MetaMask
            </Button>
            <Text fontSize="sm" w='100%' my='2' align='center'> not connected </Text>
        </Box>  

        )}
        </div>
    )
  }

export default ConnectMetamask

```

STEP 2: define a `injected` connector in `uitls/connectors.tsx`:

``` js
import { InjectedConnector } from "@web3-react/injected-connector";

export const injected = new InjectedConnector({
    supportedChainIds: [
        1, 
        3, 
        4, 
        5, 
        10, 
        42, 
        31337, 
        42161
    ]
})
```

STEP 3: add a helper in `utils/helpers.tsx`

``` js
export function formatAddress(value: string, length: number = 4) {
    return `${value.substring(0, length + 2)}...${value.substring(value.length - length)}`
}
```

STEP 4: add `ConnectMetamask` component to `index.tsx`

``` js
import ConnectMetamask from 'components/ConnectMetamask'
...
      <ConnectMetamask />
```

STEP 5: run web app by running `yarn dev`

![connect wallet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r3ls65u9i5gof7sxj1jg.png) 

Explanation of what do we do here:

- We get hooks from `useWeb3React`: chainId, account, activate,deactivate, setError, active,library ,connector

- When a user clicks connect, we call `activate(injected)`. `inject` is `InjectedConnector` (mostly it means window.ethereum injected by MetaMask) that we can configure.

- When user click disconnect, we call `decativate()`.

- The library is the Ethers.js Web3Provider we can use. 

Specifically, the library is an `Ethers.js` provider which can be used to connect and read blockchain. If we want to send transaction to blockchain (write), we will need to get [Ethers.js signer](https://docs.ethers.io/v5/api/signer/) by call `provider.getSigner()`.

---

![Task #3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1zs6513yhaz3ji6rgqk4.png)
 

## Task 3: Read from blockchain - ETHBalance

We will use Web3-React to read from smart contract.

### Task 3.1: Add `ETHbalance.tsx` (first attempt)

Add a component to get the ETH balance of your current account. Add `components/ETHBalance.tsx`

``` js
import { useState, useEffect } from 'react'
import { useWeb3React } from '@web3-react/core'
import { Web3Provider } from '@ethersproject/providers'
import { Text} from '@chakra-ui/react'
import { formatEther } from "@ethersproject/units"

const ETHBalance = () => {
    const [ethBalance, setEthBalance] = useState<number | undefined>(undefined)
    const {account, active, library,chainId} = useWeb3React<Web3Provider>()
    const provider = library
      
    useEffect(() => {
      if(active && account){
        provider?.getBalance(account).then((result)=>{
            setEthBalance(Number(formatEther(result)))
        })
      }
    })
  
    return (
        <div>
        {active ? (
            <Text fontSize="md" w='100%' my='2' align='left'>
                ETH in account: {ethBalance?.toFixed(3)} {chainId===31337? 'Test':' '} ETH
            </Text>
        ) : (
            <Text fontSize="md" w='100%' my='2' align='left'>ETH in account:</Text>
        )}
        </div>
    )
  }

export default ETHBalance
  
```

Edit `pages/index.tsx` to display ETHBalance:
``` js
        <Box  mb={0} p={4} w='100%' borderWidth="1px" borderRadius="lg">
          <Heading my={4}  fontSize='xl'>ETH Balance</Heading>
          <ETHBalance />
        </Box>
```

The problem with this is how to constantly sync the results (ETH balance) with blockchain. Lorenzo Sicilia suggests to use `SWR` with events listening to get data more efficiently. The [SWR project homepage](https://swr.vercel.app/) says: 

> SWR is a strategy to first return the data from cache (stale), then send the fetch request (revalidate), and finally come with the up-to-date data.

> With SWR, components will get a stream of data updates constantly and automatically. The UI will always be fast and reactive.

### Task 3.2: Add `ETHBalanceSWR.tsx` (second attempt)

Add `components/ETHBalanceSWR.tsx`
``` js
import { useState, useEffect } from 'react'
import { useWeb3React } from '@web3-react/core'
import { Web3Provider } from '@ethersproject/providers'
import { Text} from '@chakra-ui/react'
import { formatEther } from "@ethersproject/units"
import useSWR from 'swr'

const fetcher = (library:any) => (...args:any) => {
  const [method, ...params] = args
  return library[method](...params)
}

const ETHBalanceSWR = () => {
    const { account, active, library,chainId} = useWeb3React<Web3Provider>()

    const { data: balance,mutate } = useSWR(['getBalance', account, 'latest'], {
      fetcher: fetcher(library),
    })
    console.log("ETHBalanceSWR",balance)

    useEffect(() => {
      if(!library) return
      
      // listen for changes on an Ethereum address
      console.log(`listening for blocks...`)
      library.on('block', () => {
        console.log('update balance...')
        mutate(undefined, true)
      })
      // remove listener when the component is unmounted
      return () => {
        library.removeAllListeners('block')
      }
      
      // trigger the effect only on component mount
      // ** changed to library prepared
    }, [library])

    return (
        <div>
        {active && balance ? (
            <Text fontSize="md" w='100%' my='2' align='left'>
              ETH in account: {parseFloat(formatEther(balance)).toFixed(3)} {chainId===31337? 'Test':' '} ETH
            </Text>
        ) : (
            <Text fontSize="md" w='100%' my='2' align='left'>ETH in account:</Text>
        )}
        </div>
    )
  }

export default ETHBalanceSWR
  
```

Add `ETHBalanceSWR` component to `index.tsx`

``` js
        <Box  mb={0} p={4} w='100%' borderWidth="1px" borderRadius="lg">
          <Heading my={4}  fontSize='xl'>ETH Balance <b>using SWR</b></Heading>
          <ETHBalanceSWR />
        </Box>
```

Explanations:

- We use SWR to fetch data, which calls `provider.getBalance( address [ , blockTag = latest ] ) ` ([Ethers docs link](https://docs.ethers.io/v5/api/providers/provider/#Provider--account-methods)). The `library` is a web3 provider. 

``` js
    const { data: balance,mutate } = useSWR(['getBalance', account, 'latest'], {
      fetcher: fetcher(library),
    })
```

- The fetcher is constructed as:

``` js
const fetcher = (library:any) => (...args:any) => {
  const [method, ...params] = args
  return library[method](...params)
}
```

- We get `mutate` of SWR to change its internal cache in the client. We mutate balance to `undefined` in every block, so SWR will query and update for us.

```
      library.on('block', () => {
        console.log('update balance...')
        mutate(undefined, true)
      })
```

- When library(provider) changes and we have a provider, the side effect (`useEffect()`) will add a listener to blockchain new block event. Block events are emitted on every block change.

Let's play with the webapp:

- Send test ETH from Hardhat local testnet  Account#0(`0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`) to Account#1(`0x70997970C51812dc3A010C7d01b50e0d17dc79C8`).

- Check that the ETH balance of the current account (Account#0) changes accordingly.

More explanations about SWR can be found at:

- Lorenzo Sicilia's blockchain tutorial: [link](https://consensys.net/blog/developers/how-to-fetch-and-update-data-from-ethereum-with-react-and-swr/) 

- SWR documents: [link](https://swr.vercel.app/docs/getting-started)

---

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

## Task 4: Read / Listen - Interact with smart contract

In this task, we will read data using SWR from smart contract. We use smart contract event listening to get updates.

### Task 4.1: Add `ERC20ABI.tsx`

Add `abi/ERC20ABI.tsx` for standard ERC20.

``` 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)"
];

```

Add `components/ReadERC20.tsx`

``` js
import React, { useEffect,useState } from 'react';
import { useWeb3React } from '@web3-react/core'
import { Web3Provider } from '@ethersproject/providers'
import {Contract} from "@ethersproject/contracts";
import { formatEther}from "@ethersproject/units"
import { Text} from '@chakra-ui/react'
import useSWR from 'swr'
import {ERC20ABI as abi} from "abi/ERC20ABI"

interface Props {
    addressContract: string
}

const fetcher = (library: Web3Provider | undefined, abi: any) => (...args:any) => {
    if (!library) return

    const [arg1, arg2, ...params] = args
    const address = arg1
    const method = arg2
    const contract = new Contract(address, abi, library)
    return contract[method](...params)
  }

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

  const {  account, active, library} = useWeb3React<Web3Provider>()

  const { data: balance, mutate } = useSWR([addressContract, 'balanceOf', account], {
    fetcher: fetcher(library, abi),
  })

useEffect( () => {
    if(!(active && account && library)) return

    const erc20:Contract = new Contract(addressContract, abi, library);
    library.getCode(addressContract).then((result:string)=>{
      //check whether it is a contract
      if(result === '0x') return

      erc20.symbol().then((result:string)=>{
          setSymbol(result)
      }).catch('error', console.error)

      erc20.totalSupply().then((result:string)=>{
          setTotalSupply(formatEther(result))
      }).catch('error', console.error);
    })
//called only when changed to active
},[active])

useEffect(() => {
    if(!(active && account && library)) return

    const erc20:Contract = new Contract(addressContract, abi, library)

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

    const fromMe = erc20.filters.Transfer(account, null)
    erc20.on(fromMe, (from, to, amount, event) => {
        console.log('Transfer|sent', { from, to, amount, event })
        mutate(undefined, true)
    })

    const toMe = erc20.filters.Transfer(null, account)
    erc20.on(toMe, (from, to, amount, event) => {
        console.log('Transfer|received', { from, to, amount, event })
        mutate(undefined, true)
    })

    // remove listener when the component is unmounted
    return () => {
        erc20.removeAllListeners(toMe)
        erc20.removeAllListeners(fromMe)
    }
    
    // trigger the effect only on component mount
  }, [active,account])


return (
    <div>
        <Text >ERC20 Contract: {addressContract}</Text>
        <Text>token totalSupply:{totalSupply} {symbol}</Text>
        <Text my={4}>ClassToken in current account:{balance
        ? parseFloat(formatEther(balance)).toFixed(1)
        : " "
        } {symbol}</Text>
    </div>
  )
}
```

Add `ReadERC20` to `index.tsx`:
``` js
  const addressContract='0x5fbdb2315678afecb367f032d93f642f64180aa3'
...
        <Box  my={4} p={4} w='100%' borderWidth="1px" borderRadius="lg">
          <Heading my={4}  fontSize='xl'>ClassToken: ERC20 Smart Contract</Heading>
          <ReadERC20 addressContract={addressContract} />
        </Box>
```

Some explanations:

- We query data from blockchain and smart contract by calling `contract.balanceOf()`.

``` js 
  const { data: balance, mutate } = useSWR([addressContract, 'balanceOf', account], {
    fetcher: fetcher(library, ERC20ABI),
  })
```

- The fetcher is constructed as:

``` js
const fetcher = (library: Web3Provider | undefined, abi: any) => (...args:any) => {
    if (!library) return

    const [arg1, arg2, ...params] = args
    const address = arg1
    const method = arg2
    const contract = new Contract(address, abi, library)
    return contract[method](...params)
  }
```

- When ethereum network connection is changed to `active`, query `symbol()` and `totalSupply`. Since these two are non-changable constants, we only query them once.

- Add listener when change to `active` or `account` change. Two listeners are added: events transfer ERC20 token to `account` and from `account`.

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

    const fromMe = erc20.filters.Transfer(account, null)
    erc20.on(fromMe, (from, to, amount, event) => {
        console.log('Transfer|sent', { from, to, amount, event })
        mutate(undefined, true)
    })

    const toMe = erc20.filters.Transfer(null, account)
    erc20.on(toMe, (from, to, amount, event) => {
        console.log('Transfer|received', { from, to, amount, event })
        mutate(undefined, true)
    })
```


Result: 

![display results](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pn2zszdi326g2rufeu0n.png)
 

---

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

## Task 5: Write - Interact with smart contract

### Task 5.1: Add a component for Transfer

In this task, we will add `TransferERC20.tsx`.

Edit `components/TransferERC20.tsx`
``` js
import React, { useState } from 'react';
import { useWeb3React } from '@web3-react/core'
import { Web3Provider } from '@ethersproject/providers'
import { Contract } from "@ethersproject/contracts";
import { parseEther }from "@ethersproject/units"
import { Button, Input , NumberInput,  NumberInputField,  FormControl,  FormLabel } from '@chakra-ui/react'
import { ERC20ABI } from "abi/ERC20ABI"

interface Props {
    addressContract: string
}

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

  const { account, active, library} = useWeb3React<Web3Provider>()

  async function transfer(event:React.FormEvent) {
    event.preventDefault()
    if(!(active && account && library)) return

    // new contract instance with **signer**
    const erc20 = new Contract(addressContract, ERC20ABI, library.getSigner());
    erc20.transfer(toAddress,parseEther(amount)).catch('error', console.error)
  }

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

  return (
    <div>
        <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={!account}>Transfer</Button>
          </FormControl>
        </form>
    </div>
  )
}

```

### Task 5.2 Add transfer component to `index.tsx`

Add `TransferERC20` in `index.tsx`:

``` js
        <Box  my={4} p={4} w='100%' borderWidth="1px" borderRadius="lg">
          <Heading my={4}  fontSize='xl'>Transfer ClassToken ERC20 token</Heading>
          <TransferERC20 addressContract={addressContract} />
        </Box>

```

Let's go to `http://localhost:3000/` in browse and play with our DApp:

![Webapp](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/syu2ykzllbv4r50575r5.png)
 

---

You can find that the webapp is structured well and simply by using `Web3-React`. Web3-React gives us context provider and hooks we can use easily.

From now on, you can begin to write your own DAPPs.

---

### Tutorial List:

#### 1. A Concise Hardhat Tutorial(3 parts) 
https://dev.to/yakult/a-concise-hardhat-tutorial-part-1-7eo

#### 2. Understanding Blockchain with `Ethers.js`(5 parts)
https://dev.to/yakult/01-understanding-blockchain-with-ethersjs-4-tasks-of-basics-and-transfer-5d17

#### 3. Tutorial : build your first DAPP with Remix and Etherscan (7 Tasks)
https://dev.to/yakult/tutorial-build-your-first-dapp-with-remix-and-etherscan-52kf

#### 4. Tutorial: build DApp with Hardhat, React and Ethers.js (6 Tasks)
https://dev.to/yakult/a-tutorial-build-dapp-with-hardhat-react-and-ethersjs-1gmi

#### 5. Tutorial: build DAPP with Web3-React and SWR
https://dev.to/yakult/tutorial-build-dapp-with-web3-react-and-swr-1fb0

#### 6. Tutorial: write upgradeable smart contract (proxy) using OpenZeppelin(7 Tasks)
https://dev.to/yakult/tutorial-write-upgradeable-smart-contract-proxy-contract-with-openzeppelin-1916

#### 7. Tutorial: Build an NFT marketplace DApp like Opensea(5 Tasks)
https://dev.to/yakult/tutorial-build-a-nft-marketplace-dapp-like-opensea-3ng9



---

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