Skip to main content
Version: 3.0.0

Integrating with Web3Modal

This tutorial is a step-by-step guide on how to integrate multiple wallets such as Coinbase Wallet, Metamask, and Wallet Connect into your dapp using the web3modal library.

To explore a running version of the finished product, fork our CodeSandbox.

info

Web3Modal comes with a built-in modal interface. This is a great out-of-the-box solution, but if you would like to customize the UI of your modal, check out our tutorial on web3-react.

Example of a modal built with the web3modal library


This guide assumes you have a React application already setup and running. If you are more comfortable jumping straight into code, below is the final working example of a multi-wallet modal integration. We encourage you to fork the sandbox and reconfigure it to suit the needs of your dapp setup.

Prerequisites

Setup Web3Modal and Wallet provider options

Step 1: Install Web3Modal and an Ethereum library

Install your preferred Ethereum library for interacting with the blockchain. This tutorial will use ethers.js.

yarn add ethers # or web3
yarn add web3modal

Step 2: Instantiate Web3Modal with Wallet provider options

Install the wallet providers of your choice. Here we install Coinbase Wallet and Wallet Connect.

yarn add @coinbase/wallet-sdk
yarn add @walletconnect/web3-provider

In your App.js file, import CoinbaseWalletSDK and WalletConnect, and instantiate the various provider options to integrate into your dapp. Each provider has its own set of required parameters to pass in, such as a fallback JSON RPC URL or default chain ID. By default, Web3Modal includes the browser injected wallet (e.g. Metamask).

import CoinbaseWalletSDK from "@coinbase/wallet-sdk";
import WalletConnect from "@walletconnect/web3-provider";

export const providerOptions = {
coinbasewallet: {
package: CoinbaseWalletSDK,
options: {
appName: "Web 3 Modal Demo",
infuraId: process.env.INFURA_KEY
}
},
walletconnect: {
package: WalletConnect,
options: {
infuraId: process.env.INFURA_KEY
}
}
};
info

Seeing errors? Check out the Troubleshooting section below for help.

Step 3: Instantiate web3modal

Then, instantiate Web3Modal by passing in the provider options.

import Web3Modal from "web3modal";

const web3Modal = new Web3Modal({
providerOptions // required
});

Establish a connection to Wallet

To establish a connection to the user’s wallet, call the connect function from the Web3Modal instance. We recommend you to wrap this operation around an async function and store the retrieved library in your state to reuse throughout the app.

import { ethers } from 'ethers';
import { useState } from 'react';

function App() {

const [provider, setProvider] = useState();
const [library, setLibrary] = useState();

const connectWallet = async () => {
try {
const provider = await web3Modal.connect();
const library = new ethers.providers.Web3Provider(provider);
setProvider(provider);
setLibrary(library);
} catch (error) {
console.error(error);
}
};

return (
<div className="App">
<button onClick={connectWallet}>Connect Wallet</button>
</div>
);
}

Bind the methods onto your UI components and boom! You should now be able to connect to Coinbase Wallet and other wallets easily from your dapp.

Access connection, account, network information

Unfortunately, Web3Modal does not provide built-in support for Ethereum interactions, such as retrieving connected accounts and network data. In order to read the user’s address or connected network ID, you must directly request the information from your Ethereum library. In this example, we’ll be getting that information from ethers.js. One way is to fetch and store this data is when connecting your user to your dapp.

const [provider, setProvider] = useState();
const [library, setLibrary] = useState();
const [account, setAccount] = useState();
const [network, setNetwork] = useState();

const connectWallet = async () => {
try {
const provider = await web3Modal.connect();
const library = new ethers.providers.Web3Provider(provider);
const accounts = await library.listAccounts();
const network = await library.getNetwork();
setProvider(provider);
setLibrary(library);
if (accounts) setAccount(accounts[0]);
setNetwork(network);
} catch (error) {
console.error(error);
}
};

return (
<div className="App">
<button onClick={connectWallet}>Connect Wallet</button>
<div>Connection Status: ${!!account}</div>
<div>Wallet Address: ${account}</div>
</div>
);

The recommended way to handle change in account or network data is to subscribe to the corresponding events emitted by the user's interactions. Setup subscriptions to these events in an effect hook to react to the changes accordingly.

To see more details, search for handleAccountsChanged in App.js in the demo CodeSandbox.

useEffect(() => {
if (provider?.on) {
const handleAccountsChanged = (accounts) => {
setAccounts(accounts);
};

const handleChainChanged = (chainId) => {
setChainId(chainId);
};

const handleDisconnect = () => {
disconnect();
};

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

return () => {
if (provider.removeListener) {
provider.removeListener("accountsChanged", handleAccountsChanged);
provider.removeListener("chainChanged", handleChainChanged);
provider.removeListener("disconnect", handleDisconnect);
}
};
}
}, [provider]);

Switch networks or add custom networks

As mentioned above, Web3Modal does not have built-in support for Ethereum interactions. In order to add or switch networks, you must directly make a request (via EIP-3085 or EIP-3326) to your Ethereum library. Here is an example of requesting to switch networks and adding the network as a fallback if it is not already present on the user’s wallet:

const switchNetwork = async () => {
try {
await library.provider.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: toHex(137) }],
});
} catch (switchError) {
// This error code indicates that the chain has not been added to MetaMask.
if (switchError.code === 4902) {
try {
await library.provider.request({
method: "wallet_addEthereumChain",
params: [
{
chainId: toHex(137),
chainName: "Polygon",
rpcUrls: ["https://polygon-rpc.com/"],
blockExplorerUrls: ["https://polygonscan.com/"],
},
],
});
} catch (addError) {
throw addError;
}
}
}
};

As with the example above, any Ethereum interactions, such as sending a transaction or making a contract call, can be done by directly sending a request through the library of your choice. Take a look at an example of signing and verifying personal signatures in the CodeSandbox.

Troubleshooting

I run into the following error: Module not found: Error: Can't resolve <'buffer'/'util'/...>
Due to the removal of default polyfills in webpack5, you must install the following utilities:
yarn add buffer
yarn add util
yarn add stream-browserify
yarn add assert
yarn add stream-http
yarn add url
yarn add https-browserify
yarn add os-browserify

Then, add the following code snippet to your webpack.config.js:

resolve: {
fallback: {
'fs': false,
'stream': require.resolve('stream-browserify'),
'buffer': require.resolve('buffer/'),
'util': require.resolve('util/'),
'assert': require.resolve('assert/'),
'http': require.resolve('stream-http/'),
'url': require.resolve('url/'),
'https': require.resolve('https-browserify/'),
'os': require.resolve('os-browserify/'),
},
}

If you are using an application built on create-react-app locally, you must run npm run eject to be able to customize your webpack configuration.

The wallet connection does not persist upon refreshing the browser
Web3Modal provides a built-in option for you to automatically cache the connected provider.
// set cacheProvider parameter as true when instantiating web3modal
const web3Modal = new Web3Modal({
cacheProvider: true, // optional
providerOptions // required
});

// hook to automatically connect to the cached provider
useEffect(() => {
if (web3Modal.cachedProvider) {
connectWallet();
}
}, []);>
I want to give the user the option to "disconnect" from my dapp
Unfortunately, there is no built-in way of disconnecting a user from your dapp - the user may only choose to do so from within their wallet apps. However, you can mimic the behavior by clearing the state and cache of your application when a chooses to disconnect.
const refreshState = () => {
setAccount();
setChainId();
};

const disconnect = async () => {
await web3Modal.clearCachedProvider();
refreshState();
};

<button onClick={disconnect}>Disconnect</button>

Additional resources

Was this helpful?