Connecting to web3 with Web3Modal, react & ethers.js

I’m writing this article solely with hope that one day it ends up high in web searches for this specific problem & it saves lives of devs like me.

Or at least I will be sending that to friends starting out with building web3 apps 😄

Why should I use Web3Modal?

Web3Modal allows you to connect your app to many wallet providers in a unified way. It’s a real blessing if you’re a dev trying to develop a web3 mvp really fast, but don’t want to compromise on wallet diversity.

Here is their Github repo

How to use Web3Modal to connect to wallets?

It’s very simple! First, let’s start with an empty component

export default function Home(props) {

  return <button onClick={}>Connect</button>
}

and paste the setup code from the readme.

import Web3Modal from 'web3modal'
import WalletConnectProvider from '@walletconnect/web3-provider'

import { useState, useEffect } from 'react';

export default function Home(props) {

  const [web3Modal, setWeb3Modal] = useState(null)

  useEffect(() => {
    const providerOptions = {
      walletconnect: {
        package: WalletConnectProvider,
        options: {
          infuraId: YOUR_INFURA_KEY,
        }
      },
    };

    const newWeb3Modal = new Web3Modal({
      cacheProvider: true, // very important
      network: "mainnet",
      providerOptions,
    });

    setWeb3Modal(newWeb3Modal)
  }, [])

  return <button onClick={}>Connect</button>
}

Ok, config done. Now it’s time for the actual connection logic!

import Web3Modal from 'web3modal'
import WalletConnectProvider from '@walletconnect/web3-provider'

import { useState, useEffect } from 'react';

export default function Home(props) {

  const [web3Modal, setWeb3Modal] = useState(null)

  useEffect(() => {
    // initiate web3modal
    const providerOptions = {
      walletconnect: {
        package: WalletConnectProvider,
        options: {
          infuraId: YOUR_INFURA_KEY,
        }
      },
    };

    const newWeb3Modal = new Web3Modal({
      cacheProvider: true, // very important
      network: "mainnet",
      providerOptions,
    });

    setWeb3Modal(newWeb3Modal)
  }, [])

  async function connectWallet() {
    const provider = await web3Modal.connect();
  }

  return <button onClick={connectWallet}>Connect wallet</button>
}

Perfect! Now we have a “Connect wallet” button, which will display the web3 modal after clicking. Once the user connects their wallet, we can read their data & prompt them to perform transactions or signatures.

Integration with ethers.js (vs web3.js)

I’m not picking wars here and web3.js is a great library, but the concept of providers & signers works much better in my head so for any custom on chain interaction I’m using ethers.js

Web3Modal is not directly compatible with ethers, but you can wrap the Web3Modal provider into an ethers provider just like that:

import { ethers, providers } from "ethers";
import Web3Modal from 'web3modal'
import WalletConnectProvider from '@walletconnect/web3-provider'

import { useState, useEffect } from 'react';

export default function Home(props) {

  const [web3Modal, setWeb3Modal] = useState(null)
  const [address, setAddress] = useState("")

  useEffect(() => {
    // initiate web3modal
    ...
  }, [])

  async function connectWallet() {
    const provider = await web3Modal.connect();
    const ethersProvider = new providers.Web3Provider(provider)
    const userAddress = await ethersProvider.getSigner().getAddress()
    setAddress(userAddress)
  }

  return (
    <div>
      <button onClick={connectWallet}>Connect wallet</button>
      <p>{address}</p>
    </div>
  )
}

Connecting from cache & disconnecting

When I coded my first web3 experiments I googled this phrase a 1000 times:

ethers how to check if wallet already connected

It wasn’t really helpful as there is no standard way of doing this. Fortunately, if you enable the option cacheProvider: true in Web3Modal config, you will be able to automatically connect, without displaying the modal.

Example below!

// imports

export default function Home(props) {

  const [web3Modal, setWeb3Modal] = useState(null)
  const [address, setAddress] = useState("")

  useEffect(() => {
    // initiate web3modal
    ...
  }, [])

  useEffect(() => {
    // connect automatically and without a popup if user is already connected
    if(web3Modal && web3Modal.cachedProvider){
      connectWallet()
    }
  }, [web3Modal])

  async function connectWallet() {
    ...
  }

  return (
    <div>
      <button onClick={connectWallet}>Connect wallet</button>
      <p>{address}</p>
    </div>
  )
}

Listeners

With Web3Modal we have an easy way to subscribe to wallet events like network or account changes. I recommend doing that over playing with ethers provider, since the latter option was very messy for me.

Web3Modal listeners obey EIP-1193 standard and you can easily integrate them like in the function addListeners

// imports

export default function Home(props) {

  const [web3Modal, setWeb3Modal] = useState(null)
  const [address, setAddress] = useState("")

  useEffect(() => {
    // initiate web3modal
    ...
  }, [])

  useEffect(() => {
    // connect automatically and without a popup if user is already connected
    if(web3Modal && web3Modal.cachedProvider){
      connectWallet()
    }
  }, [web3Modal])

  async function connectWallet() {
    const provider = await web3Modal.connect();
    
    addListeners(provider);

    const ethersProvider = new providers.Web3Provider(provider)
    const userAddress = await ethersProvider.getSigner().getAddress()
    setAddress(userAddress)
  }

  async function addListeners(web3ModalProvider) {

    web3ModalProvider.on("accountsChanged", (accounts) => {
      window.location.reload()
    });
    
    // Subscribe to chainId change
    web3ModalProvider.on("chainChanged", (chainId) => {
      window.location.reload()
    });
  }


  return (
    <div>
      <button onClick={connectWallet}>Connect wallet</button>
      <p>{address}</p>
    </div>
  )
}

Now the app will automatically reload on a network or an account change. Of course you should customize it to you needs.

Summary

Hopefully this helped you with a problem or gave you a better mindset for building this integration!

If so, you should follow me on twitter or check out some cool stuff that we’re building at Mazury.

Subscribe to woj.eth
Receive the latest updates directly to your inbox.
Verification
This entry has been permanently stored onchain and signed by its creator.