# Manage User Accounts - MetaMask Connect EVM

> Connect wallets, access accounts, listen for changes, and use connectAndSign or connectWith for single-step flows in Vanilla JS or Wagmi dapps.

# 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).

  
    
  

## Prerequisites

Follow the [JavaScript quickstart](../quickstart/javascript.md) or [Wagmi quickstart](../quickstart/wagmi.md) to install and initialize the EVM client.

## Connect wallet

With Vanilla JavaScript, implement user authentication using
[`connect`](../reference/methods.md#connect) to establish a connection and get the user's accounts, and
[`accountsChanged`](../reference/provider-api.md#accountschanged) on the provider to track account switches.

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

<Tabs>
<TabItem value="Vanilla JavaScript">

```javascript

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:

```html

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

```

:::tip
After connecting, you can use [`getAccount`](../reference/methods.md#getaccount) to read the
currently selected account at any time without calling `connect` again.
:::

</TabItem>
<TabItem value="Wagmi">

```tsx

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

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

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

Wagmi provides a dedicated hook for handling account lifecycle events:

```tsx

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

  return Watching for account changes...
}
```

</TabItem>
</Tabs>

## Connect and sign

Use MetaMask Connect EVM's [`connectAndSign`](../reference/methods.md#connectandsign) method to request wallet access and sign a message in a single user interaction.
Internally, this method uses [`personal_sign`](../reference/json-rpc-api/personal_sign.mdx) to sign the message.
For example:

```js

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:

```html
<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`](../reference/methods.md#connectwith) method to request
wallet access and execute any [JSON-RPC method](../reference/json-rpc-api/index.md) in a single
user interaction. For example, connect and send a transaction at the same time:

```js

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:

```html
<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](#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](manage-networks.md).

## 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:

- [Manage networks](manage-networks.md)
- [Send transactions](send-transactions/index.md)
- [Interact with smart contracts](interact-with-contracts.md)
