The first step to onboarding users to any dApp is to connect to your user’s web3 wallet. There’s a growing amount of web3 wallets in the ecosystem and the good news is that there’s a recommended spec for interracting with EVM-compatible Layer 1 and Layer 2 chains. In other words, apps built to interract with EVM chains will do so in a universal and predictable way. This kind of spec/design doc/RFC in Ethereum is called an Ethereum Improvement Proposal (EIP).
Ethereum Improvement Proposals (EIPs) describe standards for the Ethereum platform, including core protocol specifications, client APIs, and contract standards. https://eips.ethereum.org/
Ethereum Provider API
The EIP we’re concerned about here is EIP-1193. The proposal details the specifications of the Ethereum Provider JS API, the api that enables consistent interfaces and behaviours between clients and decentralized applications.
A provider is a minimal, event-driven, protocol agnostic JavaScript object that provides access to Ethereum via a Client. Providers can easily be extended by defining new RPC methods and message
event types.
Here are some other key terms to be familiar with:
- Client: an endpoint that receives Remote Procedure Call (RPC) requests from the Provider, and returns their results.
- Wallet: an end-user application that manages private keys, performs signing operations, and acts as a middleware between the Provider and the Client.
- Remote Procedure Call (RPC): this is any request submitted to a Provider for some procedure that is to be processed by a Provider, its Wallet, or its Client.
Ethereum Convenience Libraries
Ethers.js and Web3.js are collections of modules and functions for interracting with Ethereum. After trying both, I think Ethers is easier to set up and more straightforward, but I suggest you try them both.
npm i -g @antfu/ni
ni ethers ni web3
Connecting Wallets
MetaMask
To get started with MetaMask, you need to detect if the Ethereum provider is available. When MetaMask is installed and running, it gets injected into the browser as window.ethereum
.
ni @metamask/detect-provider
import Web3 from 'web3'; import { providers } from 'ethers'; import detectEthereumProvider from '@metamask/detect-provider'; const provider = await detectEthereumProvider(); if (provider) { // Initialize Provider const web3 = new Web3(ethereum); // OR const provider = new providers.Web3Provider(ethereum); } else { console.log('Please install MetaMask!'); }
WalletConnect
WalletConnect has its own provider which you can install and initialize.
ni @walletconnect/web3-provider
import Web3 from 'web3'; import { providers } from 'ethers'; import WalletConnectProvider from "@walletconnect/web3-provider"; // Create WalletConnect Provider const provider = new WalletConnectProvider({ infuraId: ${INFURA_ID}, }); // Initialize Provider const web3 = new Web3(ethereum); // OR const provider = new providers.Web3Provider(ethereum);
Coinbase Wallet
Coinbase has its Wallet SDK for handling wallet integrations.
ni @coinbase/wallet-sdk
import Web3 from 'web3'; import { providers } from 'ethers'; import CoinbaseWalletSDK from '@coinbase/wallet-sdk' const APP_NAME = 'My Awesome App'; const APP_LOGO_URL = 'https://example.com/logo.png'; const DEFAULT_ETH_JSONRPC_URL = 'https://mainnet.infura.io/v3/<YOUR_INFURA_API_KEY>'; const DEFAULT_CHAIN_ID = 1; // Initialize Coinbase Wallet SDK const coinbaseWallet = new CoinbaseWalletSDK({ appName: APP_NAME, appLogoUrl: APP_LOGO_URL, darkMode: false }); const ethereum = coinbaseWallet.makeWeb3Provider(DEFAULT_ETH_JSONRPC_URL, DEFAULT_CHAIN_ID); // Initialize Ethereum Provider export const web3 = new Web3(ethereum); // OR export const provider = new providers.Web3Provider(ethereum);
Interracting with Wallets
Now that each wallet provider has been setup, to interract with them you’d need to call and listen for Ethereum JSON-RPC methods and events. We’ll cover some important ones but you can see the full list of events and methods here.
Retrieving Wallet Accounts
To initialize the wallet popup/modal for any of the wallets we’ve integrated above, we need to call the eth_requestAccounts
method.
import { providers } from 'ethers'; const provider = new providers.Web3Provider(window.ethereum); // OR MetaMask/WalletConnect/Coinbase Web3 provider const accounts = await provider.request({ method: 'eth_requestAccounts' }); const chainId = await provider.request({ method: 'eth_chainId' }); const account = accounts[0]; const signer = provider.getSigner();
Listening for Events
You’d need to listen for events emitted by the Ethereum provider and handle them accordingly. Here are some common events to listen for:
ethereum.on('accountsChanged', (accounts) => { // Handle the new accounts, or lack thereof. // "accounts" will always be an array, but it can be empty. }); ethereum.on('chainChanged', (chainId) => { // Handle the new chain. // Correctly handling chain changes can be complicated. // It's recommended to the page unless you have good reason not to. window.location.reload(); });
If you want a reference to how wallet integrations, methods and events look like in a Vue project, you can check out a wallet implementation here or check out the gist below for a composable function.
UI / Hook Libraries
Alternatively, if you’d like to speed things up by using a convenience library to handle wallet connections, you can check out these resources.
Troubleshooting
You might come across some errors during development, here are some resources that helped with debugging.