web3 - 3D Web3 serie
Tags: web3, javascript, vite, react
Published: 2022-08-24 15:41:00

3D web3 Series
This is the last post of 3D-web3 Series.

1 - [Vite config and basic three.js](https://dev.to/uigla/setting-up-3d-website-using-threejs-fiber-react-web3-and-vite-as-bundler-f1f)
2 - [Three.js (fiber & drei)](https://dev.to/uigla/chapter-2-3d-website-using-threejs-fiber-react-web3-and-vite-as-bundler-using-more-packages-4p02)
3 - [Cannon physics](https://dev.to/uigla/3d-web-cannon-physics-web3-serie-24jh?preview=daea07a784c169fe374948f09423bcd4d933846b1e68217029f4845626521e95552743223ea9bb5c945893066c79af233315160b6cecd6d412764d21)
**4 - 3D web - react-web3**

The stable version is v6, and currently v8 is in beta.
Is developed by Uniswap Engineering Lead Noah Zinsmeister.

At a high level, web3-react is a state machine which ensures that certain key pieces of data (the user's current account, for example) relevant to your dApp are kept up-to-date. To this end, web3-react uses Context to efficiently store this data, and inject it wherever you need it in your application.

[Web3-react v6](https://github.com/Uniswap/web3-react/tree/v6/docs#overview) uses Context to efficiently store this data, and inject it wherever you need it in your application.

Useful links:
Uniswap/web3-react [source code](https://github.com/NoahZinsmeister/web3-react/tree/v6)
Uniswap/web3-react [documents](https://github.com/NoahZinsmeister/web3-react/tree/v6/docs)
[How to Use Web3React in Your Next Project](https://hackmd.io/Ykpp1MWLTjixIZG2ZJEShA) 

There are other several libraries to create a web3 enviroment as:
[Rainbowkit](https://www.rainbowkit.com/) || [Wagmi](https://wagmi.sh/docs/getting-started) || [Scaffold-eth](https://docs.scaffoldeth.io/scaffold-eth/) || [useDApp](https://usedapp.io/) || [web3modal](https://github.com/WalletConnect/web3modal) || [Web3-UI](https://web3uikit.com/)

We are using Ethers.js behind the scene. In order to maintain context and connect with blockchain easily in our DApp. Also to connect different kind of blockchain provider, wallet or chain and to query blockchain more efficiently.

Anyway, It is possible to build directly entire DApp using ethers.

We'll be using:

MaskMask wallet on the user side
Ethereum web client libraries - "@web3-react", "ethersproject"

- From "@web3-react": Web3ReactProvider, context, useWeb3React, hooks, InjectedConnector. To connect wallet and get data from blockchain. (built in top of "ethers")
- From "@ethersproject":  Contract, Web3Provider. To send transactions to the blockchain.
- To listen broadcasted events from provider we're using "events" library.

```node
npm i @web3-react/core @web3-react/injected-connector
npm i @ethersproject/contracts @ethersproject/providers
npm i events
```

In this demo, we are deploying same token to BSC testnet and Mumbai (Polygon testnet).

First, we need to have metamask, TrustWallet (WalletConnect)or coinbase extension installed in our browser

> @Notice WalletConnect establishes an encrypted connection between your wallet and the DApp. Use it with Ex. with "Trust Wallet"
 
> You can check all css details in [git](https://github.com/unaigl/post-dev.to_three-react-web3-vite-app). This article focuses on the web3 connection.

**Step 1_** Create a web3 context to all component childs

Add context provider

> web3-react relies on the existence of a Web3ReactProvider at the root of your application.

It requires a single getLibrary prop which is responsible for instantiating a web3 convenience library object from a low-level provider.

```javascript

import React, { useEffect } from 'react';
import { Web3ReactProvider } from '@web3-react/core'
import { Web3Provider } from '@ethersproject/providers'

function getLibrary(provider) {
    const library = new Web3Provider(provider)
    library.pollingInterval = 12000
    return library
}

function Web3ContextProvider({ children }) {

    return (
        <Web3ReactProvider getLibrary={getLibrary}>
            {children}
        </Web3ReactProvider>
    )
}

export default Web3ContextProvider
```
Add to App.jsx

```javascript
import Web3ContextProvider from './web3/Web3ContextProvider';
import ConnectWallet from './web3/ConnectWallet';

return (
...
<Web3ContextProvider style={{ height: '15vh' }} className='header'>
					<ConnectWallet />
</Web3ContextProvider>
...
)
```

**Step 2_** Instantiate Web3 object and define methods

Create a ConnectWallet.jsx
Use "useWeb3React" to connect to blockchain using "InjectedConnector". 
With MetaMask you can reach a provider by "windows.ethereum".

Provide a button to connect and disconnect wallet and another one to change current chain. 

```javascript
import { useEffect } from 'react'
import { useWeb3React } from '@web3-react/core'
import { InjectedConnector } from '@web3-react/injected-connector'
import "../App.css"
import "../Button.css"
import "../Select.css"
import { changeChainById } from "./transaction/chains"
import ClaimToken from "./ClaimToken"

const ConnectWallet = () => {

    const injectedConnector = new InjectedConnector({
        supportedChainIds: [1, 97, 80001],
    })

    const { chainId, account, activate, active, library, deactivate, connector } = useWeb3React()

    const activateWallet = () => {
        activate(injectedConnector)
    }
    const deactivateWallet = () => {
        deactivate(injectedConnector)
    }
    const changeChain = (_chainID) => {
        changeChainById(_chainID)
    }

    useEffect(() => {
        if (!chainId) return
        document.getElementById('select-form').value = chainId
    }, [chainId])

    return (
        <main className="web3-navbar">
            <h2 >Welcome to 3D web3 series</h2>
            <div className='connect-box'>
                <b>ChainId: {chainId}</b>
                <div>Account: {account}</div>
                {active ? (
                    <button type="button" className='button-4' onClick={deactivateWallet}>
                        Disconnect
                    </button>
                ) : (
                    <button type="button" className='button-3' onClick={activateWallet}>
                        Connect Wallet
                    </button>
                )}
            </div>
            <div className='box'>
                <select id='select-form' onChange={e => {
                    let _chainID = e.target.value
                    changeChain(_chainID)
                }}>
                    <option key={1} value={1}>Ethereum Chain</option>
                    <option key={97} value={97}>BSC testnet</option>
                    <option key={80001} value={80001}>Mumbai testnet</option>
                </select>
            </div>
            <div>
                <ClaimToken
                    account={account}
                    chainId={chainId}
                />
            </div>
        </main>
    )
}

export default ConnectWallet
```

**Step 3_** Methods to add and change between Chains.

To switch between diferent blockchains we're using [metamask RPC API's](https://docs.metamask.io/guide/rpc-api.html#table-of-contents) has built methods.

Call "wallet_switchEthereumChain" method to request user to change chain. 

In case user hasn't that specific chain configured, we catch and call "wallet_addEthereumChain" method to request user to add selected chain.

@Notice. Use tryCatch. There are some recurrent errors to handle it [here](https://hackmd.io/Ykpp1MWLTjixIZG2ZJEShA#setError)

In order to change/add chain, we have to provide chain info. Check in code.

```javascript
export const changeChainById = async (chainID) => {
  if (!window.ethereum)
    return alert("install metamask extension in your browser");
  try {
    await ethereum.request({
      method: "wallet_switchEthereumChain",
      params: [{ chainId: chains[chainID].chainId }],
    });
  } catch (switchError) {
    // This error code indicates that the chain has not been added to MetaMask.
    if (switchError.code === 4902) {
      try {
        await ethereum.request({
          method: "wallet_addEthereumChain",
          params: [chains[chainID]],
        });
      } catch (addError) {
        console.log("error: ", addError);
        if (ex.code === 32002)
          return alert("already pending request from user in metamask");
        else
          return alert(
            "Disconnect wallet from metamask configuration and try again!"
          );
      }
    }
    // handle other "switch" errors
  }
  return;
};

const ETH = {
  name: "Ether",
  symbol: "ETH",
  decimals: 18,
};
const MATIC = {
  name: "Matic",
  symbol: "MATIC",
  decimals: 18,
};
const BNB = {
  name: "Binance",
  symbol: "BNB",
  decimals: 18,
};

const chains = {
  1: {
    chainId: "0x1",
    chainName: "Ethereum mainnet",
    nativeCurrency: ETH,
    rpcUrls: [
      import.meta.env.VITE_APP_INFURA_KEY
        ? `https://mainnet.infura.io/v3/${import.meta.env.VITE_APP_INFURA_KEY}`
        : undefined,
      import.meta.env.VITE_APP_ALCHEMY_KEY
        ? `https://eth-mainnet.alchemyapi.io/v2/${
            import.meta.env.VITE_APP_ALCHEMY_KEY
          }`
        : undefined,
      "https://cloudflare-eth.com",
    ].filter((url) => url !== undefined),
    blockExplorerUrls: ["https://etherscan.com/"],
  },
  97: {
    chainId: "0x61",
    chainName: "Binance Testnet",
    nativeCurrency: BNB,
    rpcUrls: [
      "https://data-seed-prebsc-1-s1.binance.org:8545/",
      "https://data-seed-prebsc-2-s1.binance.org:8545/",
      "http://data-seed-prebsc-1-s2.binance.org:8545/",
      "https://data-seed-prebsc-2-s3.binance.org:8545/",
    ],
    // rpcUrls: 'https://data-seed-prebsc-1-s1.binance.org:8545',
    blockExplorerUrls: ["https://testnet.bscscan.com/"],
  },
  80001: {
    chainId: "0x13881",
    chainName: "Polygon Mumbai",
    nativeCurrency: MATIC,
    rpcUrls: [
      import.meta.env.VITE_APP_INFURA_KEY
        ? `https://polygon-mumbai.infura.io/v3/${
            import.meta.env.VITE_APP_INFURA_KEY
          }`
        : undefined,
    ].filter((url) => url !== undefined),
    blockExplorerUrls: ["https://mumbai.polygonscan.com/"],
  },
};

```

**Step 4_** Define transactions

Create a ClaimToken.jsx component to define UI.

```javascript

import { burnToken, claimToken } from './transaction/transaction'
import "../App.css"

export default function TransactionMetaMask(props) {

    const claimTokenTx = () => {
        if (props.chainId === 97 || props.chainId === 80001) {
            claimToken(props.provider, props.account, props.chainId, 1)
        } else {
            scrollTo(0, 0)
            alert('Tokens are only available in BSC and Polygon testnets')
        }
    }
    const burnTokenTx = () => {
        if (props.chainId === 97 || props.chainId === 80001) {
            burnToken(props.provider, props.account, props.chainId, 1)
        } else {
            scrollTo(0, 0)
            alert('Tokens are only available in BSC and Polygon testnets')
        }
    }

    return (
        <div className='token-buttons'>
            <button type="button" className='button-3' onClick={claimTokenTx}>
                Claim Token
            </button>
            <button type="button" className='button-3' onClick={burnTokenTx}>
                Burn Token
            </button>
        </div>
    )
}

```

To be able to send a transaction to modify blockchain data, import a provider directly from "@ethersproject/providers" to be able to create "signer" object.

Now, using smart contract address, ABI and signer, create "Contract" object (ready to interact with contract methods)

```javascript
import { Contract } from "@ethersproject/contracts";
import { Web3Provider } from "@ethersproject/providers";

// Same ABI for all SC living in EVM compatible networks
export const contractAbi = [...];

const contractsAddress = {
  80001: "0x41e6913ce749018910e45980996dac1f99012c96", // MUMBAI
  97: "0x6ec4c5ce6cc67729d89785f715e103e5981c9780", // BSC Test
};
// TODO
export const getContract = (chainId) => {
  // using ethersproject to set signer using default provider
  const provider = new Web3Provider(window.ethereum);
  const signer = provider.getSigner();

  const contractAddress = contractsAddress[chainId];

  const contract = new Contract(contractAddress, contractAbi, signer);
  return contract;
};

```

Last, send asyncronous RPC and catch all the errors.

```javascript
import { getContract } from "./contract";

// writeToContractUsingWeb3React
const claimToken = async (account, chainId, amount) => {
  try {
    const myContract = getContract(chainId);
    // Metamask calculates gas, but, for walletConnect and coinbase we need to set gas limit
    const overrides = {
      gasLimit: 230000,
    };
    const txResponse = await myContract.mint(account, amount, overrides);
    const txReceipt = await txResponse.wait();
    console.log(txReceipt);
    // alert(txReceipt);
  } catch (ex) {
    console.log(ex);
    if (ex.code === 32002)
      return alert("already pending request from user in metamask");

    if (ex.code === 4001) return alert("User denied transaction signature");
    return alert('"Connect / Disconnect" your wallet and try again.');
  }
};

const burnToken = async (chainId, amount) => {
  try {
    const myContract = getContract(chainId);
    // Metamask calculates gas, but, for walletConnect and coinbase we need to set gas limit
    const overrides = {
      gasLimit: 230000,
    };
    const txResponse = await myContract.burn(amount, overrides);
    const txReceipt = await txResponse.wait();
    console.log(txReceipt);
    // alert(txReceipt);
  } catch (ex) {
    console.log(ex);
    if (ex.code === 32002)
      return alert("already pending request from user in metamask");
    if (ex.code === 4001) return alert("User denied transaction signature");
    return alert('"Connect / Disconnect" your wallet and try again.');
  }
};

export { claimToken, burnToken };

```

Now, fork [git](https://github.com/unaigl/post-dev.to_three-react-web3-vite-app) repo and try localy.

```node
npm install

// add to .env.local
// VITE_APP_INFURA_KEY
// VITE_APP_ALCHEMY_KEY

npm run dev
```
Or check Code Sand Box demo.
RPC provider could be disabled time to time.
In this case, provide your own free RPC API from [infura ](https://infura.io/) or [alchemy](https://www.alchemy.com/)

BSC testnet token address, [bscscan](https://testnet.bscscan.com/address/0x6ec4c5ce6cc67729d89785f715e103e5981c9780)
Mumbai token address, [polygonscan ](https://mumbai.polygonscan.com/address/0x41e6913ce749018910e45980996dac1f99012c96)

{% embed https://codesandbox.io/embed/three-react-web3-part-4-bu8z96?fontsize=14&hidenavigation=1&theme=dark %}

I hope it has been helpful.