Skip to content

Latest commit

 

History

History
360 lines (292 loc) · 10.4 KB

File metadata and controls

360 lines (292 loc) · 10.4 KB
title Manage User Accounts - MetaMask Connect EVM
sidebar_label Manage user accounts
description Connect wallets, access accounts, listen for changes, and use connectAndSign or connectWith for single-step flows in Vanilla JS or Wagmi dapps.
keywords
SDK
JavaScript
wagmi
authenticate
connect
sign
accounts
wallet
dapp
eth_requestAccounts
accountsChanged
session management
wallet connection
user authentication
toc_max_heading_level 3

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

Manage user accounts

Use MetaMask Connect EVM to connect wallets, retrieve user accounts, and handle session lifecycle in Vanilla JavaScript or Wagmi dapps. MetaMask Connect EVM provides connect for wallet access, connectAndSign and connectWith for single-step flows, and the accountsChanged event for tracking when users switch accounts.

With MetaMask Connect EVM, you can:

  • Connect users' wallets to your dapp.
  • Access user accounts (addresses).
  • Connect and sign in a single user interaction.
  • Connect and execute a JSON-RPC method in a single user interaction.
  • Handle connection states (connected/disconnected).
  • Listen for account changes in real time.
  • Manage wallet sessions (connect/disconnect).
  • Support multiple wallet types (extension, mobile app).

MetaMask Connect EVM wallet connection flow demonstration

Prerequisites

Follow the JavaScript quickstart or Wagmi quickstart to install and initialize the EVM client.

Connect wallet

With Vanilla JavaScript, implement user authentication using connect to establish a connection and get the user's accounts, and accountsChanged on the provider to track account switches.

With Wagmi, use the provided hooks for handling wallet connections.

import { createEVMClient } from '@metamask/connect-evm'

const evmClient = await createEVMClient({
  dapp: {
    name: 'MetaMask Connect EVM Example',
    url: window.location.href,
    iconUrl: 'https://mydapp.com/icon.png', // Optional
  },
  api: {
    supportedNetworks: {
      '0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
      '0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
    },
  },
})

// Connect wallet
async function connectWallet() {
  try {
    // Disable button while request is pending
    document.getElementById('connectBtn').disabled = true

    const { accounts, chainId } = await evmClient.connect({
      chainIds: ['0x1', '0xaa36a7'],
    })

    const account = accounts[0]
    console.log('Connected:', account, 'Chain:', chainId)

    // Update UI
    document.getElementById('status').textContent = `Connected: ${account} (chain ${chainId})`
    document.getElementById('connectBtn').style.display = 'none'
    document.getElementById('disconnectBtn').style.display = 'block'
  } catch (err) {
    if (err.code === 4001) {
      console.log('User rejected connection')
    } else {
      console.error(err)
    }
  } finally {
    document.getElementById('connectBtn').disabled = false
  }
}

// Disconnect wallet
async function disconnectWallet() {
  try {
    await evmClient.disconnect()
  } catch (err) {
    console.error('Error with disconnecting:', err)
  }
}

// Handle account changes
const provider = evmClient.getProvider()
provider.on('accountsChanged', accounts => {
  if (accounts.length === 0) {
    // User disconnected
    document.getElementById('status').textContent = 'Not connected'
    document.getElementById('connectBtn').style.display = 'block'
    document.getElementById('disconnectBtn').style.display = 'none'
  } else {
    // Account changed
    document.getElementById('status').textContent = `Connected: ${accounts[0]}`
  }
})

Display connect and disconnect buttons in HTML:

<div>
  <div id="status">Not connected</div>
  <button id="connectBtn" onclick="connectWallet()">Connect MetaMask</button>
  <button id="disconnectBtn" style="display: none" onclick="disconnectWallet()">Disconnect</button>
</div>

:::tip After connecting, you can use getAccount to read the currently selected account at any time without calling connect again. :::

import { useAccount, useConnect, useDisconnect } from 'wagmi'

function ConnectWallet() {
  const { address, isConnected } = useAccount()
  const { connectors, connect, isPending } = useConnect()
  const { disconnect } = useDisconnect()

  if (isConnected) {
    return (
      <div>
        <div>Connected to {address}</div>
        <button onClick={() => disconnect()}>Disconnect</button>
      </div>
    )
  }

  return (
    <div>
      {connectors.map(connector => (
        <button key={connector.uid} onClick={() => connect({ connector })} disabled={isPending}>
          {isPending ? 'Connecting...' : `Connect ${connector.name}`}
        </button>
      ))}
    </div>
  )
}

Wagmi provides a dedicated hook for handling account lifecycle events:

import { useAccountEffect } from 'wagmi'

function WatchAccount() {
  useAccountEffect({
    onConnect(data) {
      console.log('Connected!', {
        address: data.address,
        chainId: data.chainId,
        isReconnected: data.isReconnected,
      })
    },
    onDisconnect() {
      console.log('Disconnected!')
    },
  })

  return <div>Watching for account changes...</div>
}

Connect and sign

Use MetaMask Connect EVM's connectAndSign method to request wallet access and sign a message in a single user interaction. Internally, this method uses personal_sign to sign the message. For example:

import { createEVMClient } from '@metamask/connect-evm'

const evmClient = await createEVMClient({
  dapp: {
    name: 'MetaMask Connect EVM Example',
    url: window.location.href,
    iconUrl: 'https://mydapp.com/icon.png', // Optional
  },
  api: {
    supportedNetworks: {
      '0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
      '0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
    },
  },
})

async function handleConnectAndSign() {
  try {
    const { accounts, chainId, signature } = await evmClient.connectAndSign({
      message: 'Hello in one go!',
    })
    console.log('Accounts:', accounts, 'Chain:', chainId, 'Signature:', signature)
  } catch (err) {
    console.error('Error with connectAndSign:', err)
  }
}

document.getElementById('connectSignBtn').addEventListener('click', handleConnectAndSign)

The following HTML displays a Connect & Sign button:

<button id="connectSignBtn">Connect & Sign</button>

:::tip This one-step flow is unique to MetaMask Connect EVM's connectAndSign method. It's not part of Wagmi or other wallet libraries. :::

Connect and execute

Use MetaMask Connect EVM's connectWith method to request wallet access and execute any JSON-RPC method in a single user interaction. For example, connect and send a transaction at the same time:

import { createEVMClient } from '@metamask/connect-evm'

const evmClient = await createEVMClient({
  dapp: {
    name: 'MetaMask Connect EVM Example',
    url: window.location.href,
    iconUrl: 'https://mydapp.com/icon.png', // Optional
  },
  api: {
    supportedNetworks: {
      '0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
      '0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
    },
  },
})

async function handleConnectAndSend() {
  try {
    const {
      accounts,
      chainId,
      result: txHash,
    } = await evmClient.connectWith({
      method: 'eth_sendTransaction',
      params: account => [
        {
          from: account,
          to: '0xRecipientAddress',
          value: '0x2386F26FC10000',
        },
      ],
      chainIds: ['0x1'],
    })
    console.log('Accounts:', accounts, 'Chain:', chainId, 'Transaction hash:', txHash)
  } catch (err) {
    console.error('Error with connectWith:', err)
  }
}

document.getElementById('connectExecuteBtn').addEventListener('click', handleConnectAndSend)

The following HTML displays a Connect & Send button:

<button id="connectExecuteBtn">Connect & Send</button>

:::tip This one-step flow is unique to MetaMask Connect EVM's connectWith method. It's not part of Wagmi or other wallet libraries. :::

Best practices

Follow these best practices when authenticating users.

User interaction

  • Only trigger connection requests in response to user actions (like selecting a button).
  • Never auto-connect on page load.
  • Provide clear feedback during connection states.

Error handling

  • Handle common errors like user rejection (code 4001).
  • Provide clear error messages to users.
  • Fall back gracefully when MetaMask is not installed.

Account changes

  • Always listen for account changes.
  • Update your UI when accounts change.
  • Handle disconnection events.

Chain support

  • Listen for network/chain changes.
  • Verify the current chain meets your requirements.
  • Provide clear messaging when users need to switch networks.

Learn how to manage networks.

Common errors

The following table lists common authentication errors and their codes:

Error code Description Solution
4001 User rejected request Show a message asking the user to approve the connection.
-32002 Request already pending Disable the connect button while the request is pending.
-32603 Internal JSON-RPC error Check if MetaMask is properly installed.

Next steps

See the following guides to add more functionality to your dapp: