Creating Seamless Web3 Wallet Connections in Your DApp Using Wagmi Hooks

Table of Contents

  1. Introduction

  2. What is Wagmi

  3. Component Structure

    1. Main

    2. WalletConnection

    3. WalletConnectionModal

    4. UserAccountDetail

  4. Main Config Hooks

  5. WalletConnection Component

  6. WalletConnectionModal Component

  7. Rabby Wallet

  8. Conclusion

Introduction

A view of the Web3 wallet connection implementation we'll be discussing
A view of the Web3 wallet connection implementation we'll be discussing

For a Web3 application, a wallet connection is essential. It's the equivalent of a user signing in with a username and password, except using a wallet as a form of identification. By connecting your wallet to an app, you have the ability to sign transactions and interact with the app in a meaningful way.

This blog post will cover how to implement a Web3 wallet connection. It’s how it was implemented on the AirSwap Member Dashboard app.

With this setup, a user clicks the “CONNECT” button, which opens a Modal containing various wallets for a user to chose from. This setup relies on TypeScript, React, and the Wagmi library for Ethereum connections.

What is Wagmi

Wagmi is a collection of React Hooks containing everything you need to start working with Ethereum. It makes it easy to "Connect Wallet," display ENS and balance information, sign messages, interact with contracts, and much more — all with caching, request deduplication, and persistence. You can see more on the official docs: https://wagmi.sh

Component structure

File structure of the Wallet Connection components, except main.tsx
File structure of the Wallet Connection components, except main.tsx

There are 4 major components involved:

  1. Main - Code. This is the main React component of the project, but it contains 2 hooks with important configuration data.

  2. WalletConnection - Code. This component is responsible for rendering the “CONNECT” button, and contains logic for whether or not to render the Wallet Connection Modal.

  3. WalletConnection Modal - Code. This component is responsible rendering a selection of wallets that a user can use to connect to the application.

  4. UserAccountDetail - Code. This component it’s mostly for aesthetic purposes. It’s used to render a user’s ENS avatar, and a disconnect button.

Main config hooks

In our main React file, Main.tsx, there are 2 main hooks that we use to set up our configuration, and 1 wrapper that we place around our entire app.

configureChains

The first hook is called configureChains. This allows you to set up specific chains you want your app to use, as well as setting up your RPC providers. There’s also optional functionality in this hook that lets you setup batched multi-calls, but that’s beyond the scope of this blog post.

const { chains, publicClient, webSocketPublicClient } = configureChains(
  [mainnet, goerli, avalanche, bsc, polygon],
  [
    infuraProvider({ apiKey: import.meta.env.VITE_INFURA_API_KEY || "" }),
    publicProvider(),
  ],
);

In the code snippet above, configureChains takes in arguments of an array of chains, and an array of RPC providers. The array of chains includes values like mainnet, goerli, and avalanche. This means that our app will support those chains. The value infuraProvider configures our app to use Infura as an RPC provider.

configureChains returns several values which get passed into the createConfighook.

createConfig

createConfig is a hook where you can setup more options, such as the connectors you want to use. “Connectors” in this context is a technical term for wallets. You can pass in popular options such as MetaMask and Coinbase Wallet. createConfig also allows you to select options such as auto-connect, or to pass in a web socket connection.

const config = createConfig({
  autoConnect: true,
  publicClient,
  webSocketPublicClient,
  queryClient,
  connectors: [
    new MetaMaskConnector({ chains }),
    new InjectedConnector({ chains }),
    new CoinbaseWalletConnector({
      chains,
      options: {
        appName: "AirSwap Member Dashboard",
        appLogoUrl: AirSwapLogo,
      },
    }),
    new WalletConnectConnector({
      chains,
      options: {
        projectId: import.meta.env.VITE_WALLETCONNECT_ID,
      },
    }),
  ],
});

WalletConnection Component

Clicking this “connect” button will open the WalletConnectionModal
Clicking this “connect” button will open the WalletConnectionModal

The variable name of this component is WalletConnection. It renders 3 things:

  1. A “connect” button when the user isn’t connected. If the user is connected, the button shows the wallet address or ENS name.

  2. WalletConnectionModal, which contains is where the user can select a wallet to connect.

{showConnectionModal && (
   <WalletConnectionModal
      setShowConnectionModal={setShowConnectionModal}
   />
)}

showConnectionModal gets set to true when a user clicks the “connect” button, but isn’t yet connected to the app.

  1. It renders UserAccountDetail if the user’s wallet is connected. Wagmi has a hook called useAccount, which returns a boolean value isConnected. We can use the variable isConnected to show or hide things in our JSX code.
{isConnected && (
   <UserAccountDetail
      setShowUserAccountDetail={setShowUserAccountDetail}
      showUserAccountDetail={showUserAccountDetail}
   />
)}

**handleShowConnectionModal**

This function gets called when a user isn’t yet connected, clicks the “connect” button, but hasn’t yet connected to a chosen wallet. This function changes the values of showConnectionModal and showUserAccountDetail. These 2 variables control the visibility of WalletConnectionModal and UserAccountDetail, respectively.

const handleShowConnectionModal = () => {
   !isConnected
      ? setShowConnectionModal(true)
      : setShowUserAccountDetail(!showUserAccountDetail);
};

WalletConnectionModal Component

This component will be displayed after a user clicks the “connect” button. It displays various wallets that a user can use to connect to the application.

Within the JSX code of WalletConnectionModal, there’s an unnamed function that loops over an array of connectors. connectors is an array containing all the wallets that users can connect to, such as MetaMask or Coinbase Wallet.

{connectors
   .filter(
      (connector) =>
         connector.ready &&
         // Don't show inject if it's MetaMask (we're already showing it)
         !(connector.id === "injected" && connector.name === "MetaMask"),
      )
      .map((connector: Connector) => {
         const isInjected = connector.id === "injected";
         return (
            <button
               className="flex flex-row items-center rounded border border-gray-800 bg-gray-900 p-4 hover:bg-gray-800 disabled:cursor-not-allowed font-medium",
               )}
               disabled={!connector.ready}
               onClick={() => connect({ connector })}
               key={connector.id}
             >

The primary aspect of the function involves a map function that iterates over the connectors array and renders each one onto its own button.. Within this function there’s extra logic that checks if it’s an “injected” connector. More on injected connectors in the next section.

Rabby Wallet

Rabby is a formidable competitor to MetaMask
Rabby is a formidable competitor to MetaMask

Rabby is a competing wallet to MetaMask. It has more advanced features such as security warnings, and the ability to import multiple private keys into 1 installation. However, enabling Rabby presented some challenges that I’ll cover below.

What is an Injected Connector?

An injected connector refers to a user's default wallet. It's the wallet that a DApp will use if no other wallet is connected. If you're using Rabby, there's a switch you can toggle that sets it to your injected connector.

In the bottom right of the Rabby browser extension, clicking the “Flip” button will toggle it to MetaMask. Currently, Rabby is the injected connector.
In the bottom right of the Rabby browser extension, clicking the “Flip” button will toggle it to MetaMask. Currently, Rabby is the injected connector.
Here MetaMask has been toggled, so MetaMask will be the injected connector. Clicking “Flip” will switch it back to Rabby.
Here MetaMask has been toggled, so MetaMask will be the injected connector. Clicking “Flip” will switch it back to Rabby.

If you look again at the code in WalletConnectionModal, you’ll see 3 different things related to the injected connector:

  1. A boolean value called injectedIsMetamask, which is a React state variable. This value gets set in a useEffect after checking whether or not Rabby wallet is active. If injectedIsMetaMask is true, Rabby will be shown instead of MetaMask on the connection modal.

  2. A useEffect hook checking if the connecting wallet is using Rabby. Here’s the code used for that check:

useEffect(() => {
    const rabbyConnector = connectors.find(
      (connector) => connector.name.toLowerCase() === "rabby wallet",
    );
    if (rabbyConnector && !rabbyConnector.options.getProvider()._isRabby) {
      // Rabby is forwarding to metamask
      setInjectedIsMetaMask(true);
    }
  }, [connectors]);

Note that on the rabbyConnector object, if the getProvider method does not contain _isRabby, then we set the value is injectedIsMetaMask to be true.

  1. In the JSX before the map function, we filter through the connectors array before looping over it with the map method. Here, we’re filtering to see if MetaMask is the injected wallet connector. It gets filtered out, because in our main.tsx component, we’re adding in MetaMask to our connectors array in the createConfig hook. (Scroll up the section “Main config hooks” to see that code snippet).

Additional logic in WalletConnectionModal

There’s another useEffect hook in this component:

useEffect(() => {
    isConnected && setShowConnectionModal(false);
    // close modal when WalletConnect or Coinbase modal is open
    pendingConnector && setShowConnectionModal(false);
  }, [isConnected, pendingConnector, setShowConnectionModal]);

This code was added after a small bug was discovered with WalletConnect, Coinbase Wallet, and any other wallet that uses its own modal.

When connecting to WalletConnect, we found that the WalletConnectionModal was overlaying:

The code snippet above fixed this bug, so WalletConnect (and Coinbase Wallet) rendered correctly like so:

UserAccountDetail Component

This component is mostly for aesthetic purposes. There are 2 functional aspects worth mentioning:

  1. Disconnect function: This utilizes the Wagmi hook useDisconnect. Here’s the code: const { disconnect } = useDisconnect();. Calling the disconnect function on the button will log out the user.

  2. ENS Avatar: this is a nice touch if a user has an ENS name associated with an address. It builds familiarity and trust with the app. Here’s the code:

const { data: ensName } = useEnsName({ address, chainId: 1 });
const { data: avatarUrl } = useEnsAvatar({
   name: ensName,
   chainId: 1,
});

With this code in place, we can use avatarUrl (which is returned from the useEnsAvatar hook, to render a user’s avatar:

The red arrow points to the avatar URL
The red arrow points to the avatar URL

Video Demo

Conclusion

In this post we covered a technical explanation of how to implement a Web3 wallet connection using Wagmi hooks and React.

We explored key components such as the Main Config Hooks, Wallet Connection Component, and Wallet Connection Modal, each playing a vital role in creating a seamless and secure user experience within decentralized applications.

This demonstrates the simplicity of the implementation. Let me know if my explanation wasn’t simple enough, or what I can do to improve. Thanks for reading!

App: https://dao.airswap.eth.limo

Subscribe to starrdev
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.