Skip to main content

You can connect your dapp to users' MetaMask wallets by detecting MetaMask in their browsers and establishing a connection to their accounts. This page explains how to connect to MetaMask using the wallet detection mechanism introduced by EIP-6963. This approach enables you to detect multiple installed wallets and connect to them without conflicts.

There are two main methods to connect to MetaMask: using third-party libraries or directly using Vite.

Connect to MetaMask using third-party libraries

You can connect to MetaMask using the following third-party libraries that support EIP-6963:

Connect to MetaMask directly using Vite

To connect to MetaMask directly, we recommend implementing support for EIP-6963 using the Vite build tool with either vanilla TypeScript or React TypeScript.

Vanilla TypeScript

Follow these steps to create a vanilla TypeScript project that connects to MetaMask:

1. Create a project

Set up a Vite project using the vanilla TypeScript template:

npm create vite@latest vanilla-ts-6963 -- --template vanilla-ts

2. Set up the project

In your Vite project, update src/vite-env.d.ts with the EIP-6963 interfaces:

/// <reference types="vite/client" />

interface EIP6963ProviderInfo {
rdns: string
uuid: string
name: string
icon: string
}

interface EIP6963ProviderDetail {
info: EIP6963ProviderInfo
provider: EIP1193Provider
}

type EIP6963AnnounceProviderEvent = {
detail: {
info: EIP6963ProviderInfo
provider: Readonly<EIP1193Provider>
}
}

interface EIP1193Provider {
isStatus?: boolean
host?: string
path?: string
sendAsync?: (
request: { method: string; params?: Array<unknown> },
callback: (error: Error | null, response: unknown) => void
) => void
send?: (
request: { method: string; params?: Array<unknown> },
callback: (error: Error | null, response: unknown) => void
) => void
request: (request: {
method: string
params?: Array<unknown>
}) => Promise<unknown>
}

Note: In addition to the EIP-6963 interfaces, you need an EIP1193Provider interface (defined by EIP-1193). This interface is the foundational structure for Ethereum wallet providers and represents the essential properties and methods for interacting with MetaMask and other Ethereum wallets in JavaScript.

3. Update main.ts

Update src/main.ts with the following code:

import "./style.css"
import { listProviders } from "./providers.ts"

document.querySelector<HTMLDivElement>("#app")!.innerHTML = `
<div>
<div id="providerButtons"></div>
</div>
`

listProviders(document.querySelector<HTMLDivElement>("#providerButtons")!)

The querySelector finds and returns the first HTML element that matches the CSS selector #app, and sets its innerHTML. Include a basic HTML structure with an inner div to inject a list of buttons, each representing a detected wallet provider.

You'll create the listProviders function in the next step, passing an argument that represents the div element.

4. Connect to wallets

Create a file src/providers.ts with the following code:

declare global {
interface WindowEventMap {
"eip6963:announceProvider": CustomEvent
}
}

// Connect to the selected provider using eth_requestAccounts.
const connectWithProvider = async (
wallet: EIP6963AnnounceProviderEvent["detail"]
) => {
try {
await wallet.provider.request({ method: "eth_requestAccounts" })
} catch (error) {
console.error("Failed to connect to provider:", error)
}
}

// Display detected providers as connect buttons.
export function listProviders(element: HTMLDivElement) {
window.addEventListener(
"eip6963:announceProvider",
(event: EIP6963AnnounceProviderEvent) => {
const button = document.createElement("button")

button.innerHTML = `
<img src="${event.detail.info.icon}" alt="${event.detail.info.name}" />
<div>${event.detail.info.name}</div>
`

// Call connectWithProvider when a user selects the button.
button.onclick = () => connectWithProvider(event.detail)
element.appendChild(button)
}
)

// Notify event listeners and other parts of the dapp that a provider is requested.
window.dispatchEvent(new Event("eip6963:requestProvider"))
}

The connectWithProvider function connects the user to the selected provider using eth_requestAccounts. It takes a wallet object as an argument, which is of type EIP6963AnnounceProviderEvent["detail"].

The listProviders function uses a streamlined approach. It directly passes the event.detail object to the connectWithProvider function when a provider is announced, rather than mapping and joining an entire block of HTML.

5. View the project

Run the following command to view and test the Vite project in your browser:

npm run dev

Example

For more information, check out the vanilla TypeScript example. You can clone the repository and run the example locally using npm i && npm run dev.

React TypeScript

Follow these steps for creating a React TypeScript project to connect to MetaMask:

1. Create a project

Create a Vite project using the template for React TypeScript:

npm create vite@latest react-ts-6963 -- --template react-ts

2. Set up the project

In your Vite project, update src/vite-env.d.ts with the EIP-6963 interfaces:

/// <reference types="vite/client" />

interface EIP6963ProviderInfo {
rdns: string
uuid: string
name: string
icon: string
}

interface EIP6963ProviderDetail {
info: EIP6963ProviderInfo
provider: EIP1193Provider
}

type EIP6963AnnounceProviderEvent = {
detail: {
info: EIP6963ProviderInfo
provider: Readonly<EIP1193Provider>
}
}

interface EIP1193Provider {
isStatus?: boolean
host?: string
path?: string
sendAsync?: (
request: { method: string; params?: Array<unknown> },
callback: (error: Error | null, response: unknown) => void
) => void
send?: (
request: { method: string; params?: Array<unknown> },
callback: (error: Error | null, response: unknown) => void
) => void
request: (request: {
method: string
params?: Array<unknown>
}) => Promise<unknown>
}

Note: In addition to the EIP-6963 interfaces, you need a EIP1193Provider interface (defined by EIP-1193), which is the foundational structure for Ethereum wallet providers, and represents the essential properties and methods for interacting with MetaMask and other Ethereum wallets in JavaScript.

3. Update App.tsx

Update src/App.tsx with the following code:

import "./App.css"
import { DiscoverWalletProviders } from "./components/DiscoverWalletProviders"

function App() {
return (
<DiscoverWalletProviders/>
)
}

export default App

This code renders the DiscoverWalletProviders component that you'll create in the next step, which contains the logic for detecting and connecting to wallet providers.

4. Detect and connect to wallets

In the src/components directory, create a component DiscoverWalletProviders.tsx with the following code:

import { useState } from "react"
import { useSyncProviders } from "../hooks/useSyncProviders"
import { formatAddress } from "~/utils"

export const DiscoverWalletProviders = () => {
const [selectedWallet, setSelectedWallet] = useState<EIP6963ProviderDetail>()
const [userAccount, setUserAccount] = useState<string>("")
const providers = useSyncProviders()

// Connect to the selected provider using eth_requestAccounts.
const handleConnect = async (providerWithInfo: EIP6963ProviderDetail) => {
try {
const accounts = await providerWithInfo.provider.request({
method: "eth_requestAccounts"
})

setSelectedWallet(providerWithInfo)
setUserAccount(accounts?.[0])
} catch (error) {
console.error(error)
}
}

// Display detected providers as connect buttons.
return (
<>
<h2>Wallets Detected:</h2>
<div>
{
providers.length > 0 ? providers?.map((provider: EIP6963ProviderDetail) => (
<button key={provider.info.uuid} onClick={() => handleConnect(provider)} >
<img src={provider.info.icon} alt={provider.info.name} />
<div>{provider.info.name}</div>
</button>
)) :
<div>
No Announced Wallet Providers
</div>
}
</div>
<hr />
<h2>{userAccount ? "" : "No "}Wallet Selected</h2>
{userAccount &&
<div>
<div>
<img src={selectedWallet.info.icon} alt={selectedWallet.info.name} />
<div>{selectedWallet.info.name}</div>
<div>({formatAddress(userAccount)})</div>
</div>
</div>
}
</>
)
}

In this code:

  • selectedWallet is a state variable that holds the user's most recently selected wallet.
  • userAccount is a state variable that holds the user's connected wallet's address.
  • useSyncProviders is a custom hook that returns the providers array (wallets installed in the browser).

The handleConnect function takes a providerWithInfo, which is an EIP6963ProviderDetail object. That object is used to request the user's accounts from the provider using eth_requestAccounts.

If the request succeeds, the selectedWallet and userAccount local state variables are set.

Then, the component maps over the providers array and renders a button for each detected provider.

Finally, if the userAccount state variable is not empty, the selected wallet icon, name, and address are displayed.

5. Add React hooks

Create a src/hooks directory and add a store.ts file with the following code:

declare global {
interface WindowEventMap {
"eip6963:announceProvider": CustomEvent
}
}

// An array to store the detected wallet providers.
let providers: EIP6963ProviderDetail[] = []

export const store = {
value: () => providers,
subscribe: (callback: () => void) => {
function onAnnouncement(event: EIP6963AnnounceProviderEvent) {
if (providers.map((p) => p.info.uuid).includes(event.detail.info.uuid))
return
providers = [...providers, event.detail]
callback()
}

// Listen for eip6963:announceProvider and call onAnnouncement when the event is triggered.
window.addEventListener("eip6963:announceProvider", onAnnouncement)

// Dispatch the event, which triggers the event listener in the MetaMask wallet.
window.dispatchEvent(new Event("eip6963:requestProvider"))

// Return a function that removes the event listern.
return () =>
window.removeEventListener("eip6963:announceProvider", onAnnouncement)
},
}

Also, add a file useSyncProviders.ts with the following code to the hooks directory:

import { useSyncExternalStore } from "react"
import { store } from "./store"

export const useSyncProviders = () =>
useSyncExternalStore(store.subscribe, store.value, store.value)

This hook allows you to subscribe to MetaMask events, read updated values, and update components. It uses the store.value and store.subscribe methods defined in the store.ts hook.

6. Create utility functions

Create a src/utils directory and add a file index.ts with the following code:

export const formatBalance = (rawBalance: string) => {
const balance = (parseInt(rawBalance) / 1000000000000000000).toFixed(2)
return balance
}

export const formatChainAsNum = (chainIdHex: string) => {
const chainIdNum = parseInt(chainIdHex)
return chainIdNum
}

export const formatAddress = (addr: string) => {
const upperAfterLastTwo = addr.slice(0, 2) + addr.slice(2)
return `${upperAfterLastTwo.substring(0, 5)}...${upperAfterLastTwo.substring(39)}`
}

This directory is ideal for storing utility functions that you may need to reuse throughout your dapp. While this example only utilizes the formatAddress function, the others could prove useful for various applications.

Example

For a more comprehensive look, check out the React TypeScript example. You can clone the repository and run the example locally by executing npm i && npm run dev.