Introduction
What is Wagmi
Component Structure
Main
WalletConnection
WalletConnectionModal
UserAccountDetail
Main Config Hooks
WalletConnection Component
WalletConnectionModal Component
Rabby Wallet
Conclusion
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.
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
There are 4 major components involved:
Main - Code. This is the main React component of the project, but it contains 2 hooks with important configuration data.
WalletConnection - Code. This component is responsible for rendering the “CONNECT” button, and contains logic for whether or not to render the Wallet Connection Modal.
WalletConnection Modal - Code. This component is responsible rendering a selection of wallets that a user can use to connect to the application.
UserAccountDetail - Code. This component it’s mostly for aesthetic purposes. It’s used to render a user’s ENS avatar, and a disconnect button.
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 createConfig
hook.
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,
},
}),
],
});
The variable name of this component is WalletConnection
. It renders 3 things:
A “connect” button when the user isn’t connected. If the user is connected, the button shows the wallet address or ENS name.
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.
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);
};
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 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.
If you look again at the code in WalletConnectionModal
, you’ll see 3 different things related to the injected connector:
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.
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
.
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).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:
This component is mostly for aesthetic purposes. There are 2 functional aspects worth mentioning:
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.
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:
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!