How to Connect to Metamask Wallet in React
Tags: metamask, smartcontract, dapp, ether
Published: 2022-02-11 12:20:50

To integrate a smart contract with a frontend, the first task is usually how to connect a wallet to your frontend application. This blog post will show you how to connect a wallet from a React application.

## Packages to install
The following packages will be needed to connect the wallet:
* WalletConnectProvider
* WalletLink
* Web3Modal
* Ether (this is used to connect to the smart contract on the blockchain)

The following packages above can be installed in a React application by running the following commands on terminal:

```React
  npm i --save walletlink @walletconnect/web3-provider ethers 
  web3modal
```
After installing the following packages, open your React `App.js` file or the top most component of your application. We will firstly import the packages we just installed.

```jsx
  import { useEffect, useState, useCallback } from "react";
  import { ethers, providers } from "ethers";
  import Web3Modal from "web3modal";
  import WalletConnectProvider from '@walletconnect/web3-provider'
  import WalletLink from 'walletlink';
  
  const App = () => {
     return (
       <div>
         <h1>Hello</h1>
      </div>
     )
  }
```
This is the skeleton of a simple React component. We declared a web3Modal that will allow the select of the wallet we want to use. We need to create a provider option that will be passed to the `web3Modal`.

```jsx
  const INFURA_ID = "your-infura-api-key";
  const providerOptions = {
  walletconnect: {
    package: WalletConnectProvider, // required
    options: {
      infuraId: INFURA_ID, // required
    },
  },
  'custom-walletlink': {
    display: {
      logo: 'https://play-lh.googleusercontent.com/PjoJoG27miSglVBXoXrxBSLveV6e3EeBPpNY55aiUUBM9Q1RCETKCOqdOkX2ZydqVf0',
      name: 'Coinbase',
      description: 'Connect to Coinbase Wallet',
    },
    options: {
      appName: 'Coinbase', // Your app name
      networkUrl: `https://mainnet.infura.io/v3/${INFURA_ID}`,
      chainId: 1,
    },
    package: WalletLink,
    connector: async (_, options) => {
      const { appName, networkUrl, chainId } = options
      const walletLink = new WalletLink({
        appName,
      })
      const provider = walletLink.makeWeb3Provider(networkUrl, chainId)
      await provider.enable()
      return provider
    },
  },
}

```
The `WalletLink` package allows users to use your application in any desktop browser without installing an extension, and it established a secure tunnel between your app and the mobile wallet with end-to-end encryption utilizing client-generated keys and keeps all user activity private.

`WalletConnect` package is an open protocol that helps to communicate securely between Wallets and Dapps (Web3 Apps). The protocol establishes a remote connection between two apps and/or devices using a Bridge server to relay payloads. We need to provide an api key to connect.

```jsx
let web3Modal
if (typeof window !== 'undefined') {
  web3Modal = new Web3Modal({
    network: 'mainnet', // optional
    cacheProvider: true,
    providerOptions, // required
  })
}
```
We pass the providerOptions to the web3Modal instance. The App.jsx looks exactly like this now.

```jsx

  import { useEffect, useState, useCallback } from "react";
  import { ethers, providers } from "ethers";
  import Web3Modal from "web3modal";
  import WalletConnectProvider from '@walletconnect/web3-provider'
  import WalletLink from 'walletlink';

const INFURA_ID = "your-infura-api-key";
  const providerOptions = {
  walletconnect: {
    package: WalletConnectProvider, // required
    options: {
      infuraId: INFURA_ID, // required
    },
  },
  'custom-walletlink': {
    display: {
      logo: 'https://play-lh.googleusercontent.com/PjoJoG27miSglVBXoXrxBSLveV6e3EeBPpNY55aiUUBM9Q1RCETKCOqdOkX2ZydqVf0',
      name: 'Coinbase',
      description: 'Connect to Coinbase Wallet',
    },
    options: {
      appName: 'Coinbase', // Your app name
      networkUrl: `https://mainnet.infura.io/v3/${INFURA_ID}`,
      chainId: 1,
    },
    package: WalletLink,
    connector: async (_, options) => {
      const { appName, networkUrl, chainId } = options
      const walletLink = new WalletLink({
        appName,
      })
      const provider = walletLink.makeWeb3Provider(networkUrl, chainId)
      await provider.enable()
      return provider
    },
  },
}

 let web3Modal
if (typeof window !== 'undefined') {
  web3Modal = new Web3Modal({
    network: 'mainnet', // optional
    cacheProvider: true,
    providerOptions, // required
  })
}


  const App = () => {
     return (
       <div>
         <h1>Hello</h1>
      </div>
     )
  }

```
The provider object needs to be persistent in our application and to prevent passing props down all the component of our application. We will create a context file which we will name `walletContext.js`.

This file will contain the following contents:

```jsx
  import * as React from "react";

  const WalletContext = React.createContext();

  const accountDetails = {
     provider: null,
     address: null,
     signer: null,
     web3Provider: null,
     network: null
}

function WalletProvider({children}){
  const [account, setAccountDetails ] = 
   React.useState(accountDetails);
  const value = { account, setAccountDetails };
  return <WalletContext.Provider value={value}>{children} 
  </WalletContext.Provider>
}

function useWallet(){
    const context = React.useContext(WalletContext);
    if (!context){
        throw new Error("useWallet must be used within a WalletProvider")
    }
    return context;
}

export {WalletProvider, useWallet }

```
We created a React context and named it `WalletContext`. Then we created a `WalletProvider` function that returns the `WalletContext.Provider` component wrapped with any `children` that will be passed to it. Inside the `WalletProvider` function, we declared a React state that will be used to store the details of the connected account. The `account` state and the function to change the state `setAccountDetails` is passed to the `value` property of the `WalletContext.Provider` component. [See application state management](https://kentcdodds.com/blog/application-state-management-with-react)


We need to create a button that will be used to connect the wallet. We will define two functions which are `connect` and the `disconnect` functions. As their name suggest, they will be used to connect and disconnect and disconnect the wallet from the blockchain. Before then we need to create React state that will be used to store the value returned from the functions.

Next we created a function that will consume the created `WalletContext`. The `useWallet` hook consumes the `WalletContext` and returns the `context` for use.

```jsx
  <-- previous code above -->

   const App = () => {
    const { account, setAccountDetails } = useWallet();
    const { provider,
     address,
     signer,
     web3Provider,
     network } = account;
     
     
const connect = useCallback(async function () {
  const provider = await web3Modal.connect();
  const web3Provider = new providers.Web3Provider(provider);
  const signer = web3Provider.getSigner()
  const address = await signer.getAddress()
  const network = await web3Provider.getNetwork();
  const accountDetails = {
       provider,
       web3Provider,
       signer,
       address,
       network
  }
  setAccountDetails(accountDetails);
}, []);

const disconnect = useCallback(
  async function () {
    await web3Modal.clearCachedProvider()
    if (provider?.disconnect && typeof provider.disconnect === 'function') {
      await provider.disconnect()
    }
    //reset the state here
   const accountDetails = {
      provider: null,
      web3Provider: null,
      signer: null,
      address: null,
      network: null
 }
 setAccountDetails(accountDetails);
  },
  [provider]
)

     return (
       <div>
         <h1>Hello</h1>
      </div>
     )
  }

```
The connect function creates a provider variable that is set to the default web3Modal connection. This provider is then plugged into the provider object of `ether.js`.

`const web3Provider = new providers.Web3Provider(provider)`
We are able to use the `web3Provider`object created by `ether.js` to get the `signer`, `network`, `address` from `ether.js`. This values are saved in the context using the `setAccountDetails` function.

The `disconnect` function clears the `web3Modal` cache , the saved state and also disconnect the `provider`. We optimized both functions by wrapping them in a `useCallback` and passing dependencies.

## Auto connecting 
If we have already connected to our wallet before, we will want to automatically reconnect when visiting the page or component. This we can do by calling `connect` in a `useEffect` hook.

```jsx
  // Auto connect to the cached provider
  useEffect(() => {
    if (web3Modal.cachedProvider) {
      connect()
    }
  }, [connect]);
```

We can also listen to events on the provider object to handle some scenarios. Like we may want to be still connected, if a user switch the connected account. The function `handleAccountsChanged` does that by listening for an account changed event and updates the address portion of the `WalletContext`. 

```jsx
     useEffect(() => {
    if (provider?.on) {
      const handleAccountsChanged = (accounts) => {
        console.log('accountsChanged', accounts);
        setAccountDetails({
            ...account,
            address: accounts[0],
        })
      }
      const handleChainChanged = (_hexChainId) => {
        window.location.reload()
      }

      const handleDisconnect = (error) => {
        console.log('disconnect', error)
        disconnect()
      }

      provider.on('accountsChanged', handleAccountsChanged)
      provider.on('chainChanged', handleChainChanged)
      provider.on('disconnect', handleDisconnect)

      // Subscription Cleanup
      return () => {
        if (provider.removeListener) {
          provider.removeListener('accountsChanged', handleAccountsChanged)
          provider.removeListener('chainChanged', handleChainChanged)
          provider.removeListener('disconnect', handleDisconnect)
        }
      }
    }
  }, [provider, disconnect])

```
Putting it all together

//APP>JX
```jsx
  import { useEffect, useState, useCallback } from "react";
import { ethers, providers } from "ethers";
import Web3Modal from "web3modal";
import WalletConnectProvider from '@walletconnect/web3-provider'
import WalletLink from 'walletlink';
import { useWallet } from './walletContext';

import './App.css';

const trimAddress = ( address ) => {
  const firstpart = address.slice(0, 4);
  const midpart = "....";
  const endpart = address.slice(address.length - 4, address.length );
  return `${firstpart}${midpart}${endpart}`
}

const INFURA_ID = '460f40a260564ac4a4f4b3fffb032dad'


const providerOptions = {
  walletconnect: {
    package: WalletConnectProvider, // required
    options: {
      infuraId: INFURA_ID, // required
    },
  },
  'custom-walletlink': {
    display: {
      logo: 'https://play-lh.googleusercontent.com/PjoJoG27miSglVBXoXrxBSLveV6e3EeBPpNY55aiUUBM9Q1RCETKCOqdOkX2ZydqVf0',
      name: 'Coinbase',
      description: 'Connect to Coinbase Wallet (not Coinbase App)',
    },
    options: {
      appName: 'Coinbase', // Your app name
      networkUrl: `https://mainnet.infura.io/v3/${INFURA_ID}`,
      chainId: 1,
    },
    package: WalletLink,
    connector: async (_, options) => {
      const { appName, networkUrl, chainId } = options
      const walletLink = new WalletLink({
        appName,
      })
      const provider = walletLink.makeWeb3Provider(networkUrl, chainId)
      await provider.enable()
      return provider
    },
  },
}

let web3Modal
if (typeof window !== 'undefined') {
  web3Modal = new Web3Modal({
    network: 'mainnet', // optional
    cacheProvider: true,
    providerOptions, // required
  })
}


function App() {
  const { account, setAccountDetails } = useWallet();
  const { provider,
    address,
    signer,
    web3Provider,
    network } = account;


const connect = useCallback(async function () {
  const provider = await web3Modal.connect();
  const web3Provider = new providers.Web3Provider(provider);
  const signer = web3Provider.getSigner()
  const address = await signer.getAddress()
  const network = await web3Provider.getNetwork();
  const accountDetails = {
       provider,
       web3Provider,
       signer,
       address,
       network
  }
  setAccountDetails(accountDetails);
}, []);



const disconnect = useCallback(
  async function () {
    await web3Modal.clearCachedProvider()
    if (provider?.disconnect && typeof provider.disconnect === 'function') {
      await provider.disconnect()
    }
    //reset the state here
    const accountDetails = {
      provider: null,
      web3Provider: null,
      signer: null,
      address: null,
      network: null
 }
 setAccountDetails(accountDetails);
  
},
  [provider]
)

  // Auto connect to the cached provider
  useEffect(() => {
    if (web3Modal.cachedProvider) {
      connect()
    }
  }, [connect]);

  
  useEffect(() => {
    if (provider?.on) {
      const handleAccountsChanged = (accounts) => {
        // eslint-disable-next-line no-console
        console.log('accountsChanged', accounts);
        setAccountDetails({
            ...account,
            address: accounts[0],
        })
      }

      const handleChainChanged = (_hexChainId) => {
        window.location.reload()
      }

      const handleDisconnect = (error) => {
        console.log('disconnect', error)
        disconnect()
      }

      provider.on('accountsChanged', handleAccountsChanged)
      provider.on('chainChanged', handleChainChanged)
      provider.on('disconnect', handleDisconnect)

      // Subscription Cleanup
      return () => {
        if (provider.removeListener) {
          provider.removeListener('accountsChanged', handleAccountsChanged)
          provider.removeListener('chainChanged', handleChainChanged)
          provider.removeListener('disconnect', handleDisconnect)
        }
      }
    }
  }, [provider, disconnect])

  return (
    <div className="App">
         {web3Provider ? (
               <button className="btn btn-danger" type="button" onClick={disconnect}>
                 {trimAddress(address)}
          </button>
        ) : (
          <button className="btn btn-success" type="button" onClick={connect}>
            Connect
          </button>
        )}
    </div>
  );
}

export default App;
```
To make use of the `WalletProvider` we wrap our App component with it. This makes the `WalletContext` available in all the components that it will be needed.

```jsx
import { WalletProvider } from "./walletContext";
ReactDOM.render(
  <React.StrictMode>
    <WalletProvider>
        <App />
    </WalletProvider>
  </React.StrictMode>,
  document.getElementById('root')
```
The code is available [here](https://github.com/jamiebones/wallet_connection)

Thanks for reading.