> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# MetaMask Connection Guide

> Connect MetaMask on web and React Native through an installed extension, a desktop QR code, or an SDK-managed mobile handoff.

Use the JavaScript SDK to support EVM and Solana accounts through one of these routes:

* An installed MetaMask browser extension, on desktop or inside the MetaMask in-app browser.
* A MetaMask SDK pairing URI rendered as a QR code on desktop.
* An SDK-managed handoff to MetaMask on mobile web or React Native.

> [!NOTE]
>
> MetaMask SDK pairing is not a generic WalletConnect connection. The MetaMask
> functions open MetaMask on mobile. Use their returned URI only when rendering
> a desktop QR code.

## Prerequisites

* [Create](https://docs.dynamic.xyz/docs/javascript/reference/client/create-dynamic-client.md) and [initialize](https://docs.dynamic.xyz/docs/javascript/reference/client/initialize-dynamic-client.md) a Dynamic client.
* Enable EVM and/or Solana in the [Dynamic Console](https://console.dynamic.xyz/dashboard/chains-and-networks).
* Install `@dynamic-labs-sdk/evm` and/or `@dynamic-labs-sdk/solana`.
* On React Native, complete [React Native setup](https://docs.dynamic.xyz/docs/javascript/react-native/setup.md).

## Add the extensions

The MetaMask extensions in `@dynamic-labs-sdk/evm/metamask` and `@dynamic-labs-sdk/solana/metamask` cover only the desktop QR code and the mobile handoff. The installed MetaMask browser extension is discovered by the standard chain extensions through EIP-6963 (EVM) and Solana Wallet Standard, so add both:

> [tab] EVM
>
> ```typescript theme={"system"}
> import { addEvmExtension } from '@dynamic-labs-sdk/evm';
> import { addMetaMaskEvmExtension } from '@dynamic-labs-sdk/evm/metamask';
>
> addEvmExtension();
> addMetaMaskEvmExtension();
> ```

> [tab] Solana
>
> ```typescript theme={"system"}
> import { addSolanaExtension } from '@dynamic-labs-sdk/solana';
> import { addMetaMaskSolanaExtension } from '@dynamic-labs-sdk/solana/metamask';
>
> addSolanaExtension();
> addMetaMaskSolanaExtension();
> ```

| Route | Required extension |
| - | - |
| Installed browser extension | `addEvmExtension` / `addSolanaExtension` |
| Desktop QR code and mobile handoff | `addMetaMaskEvmExtension` / `addMetaMaskSolanaExtension` |

## Connect an installed extension

This route applies when MetaMask runs as a browser extension: a desktop browser with MetaMask installed, or your dapp opened inside the MetaMask in-app browser. On mobile web and React Native without the extension, use the URI routes below instead.

Use the chain-specific key helper to detect a MetaMask provider announced through EIP-6963 or Solana Wallet Standard. If the helper returns a key, pass it to the normal wallet provider connection flow.

```typescript theme={"system"}
import { connectAndVerifyWithWalletProvider } from '@dynamic-labs-sdk/client';
import { getMetaMaskEvmExtensionWalletProviderKey } from '@dynamic-labs-sdk/evm/metamask';

const walletProviderKey = getMetaMaskEvmExtensionWalletProviderKey();

if (walletProviderKey) {
  const walletAccount = await connectAndVerifyWithWalletProvider({
    walletProviderKey,
  });

  console.log(walletAccount.address);
}
```

For Solana, use `getMetaMaskSolanaExtensionWalletProviderKey` from `@dynamic-labs-sdk/solana/metamask`.

## Connect with a MetaMask SDK URI

The URI functions return `{ uri, approval }`:

* On desktop, render `uri` as a QR code for the user to scan with MetaMask.
* On mobile web and React Native, the SDK opens MetaMask. You do not need to use `uri`.
* Call `approval()` after starting the connection. It resolves with `{ walletAccounts }` after the user finishes the wallet interactions.

Choose the function based on whether the account must be verified during the flow:

| Chain | Connect only | Connect and verify |
| - | - | - |
| EVM | `connectWithMetaMaskUriEvm` | `connectAndVerifyWithMetaMaskUriEvm` |
| Solana | `connectWithMetaMaskUriSolana` | `connectAndVerifyWithMetaMaskUriSolana` |

The connect-only functions add an unverified wallet account by default. Pass `{ addToDynamicWalletAccounts: false }` if you only need the returned account. The connect-and-verify functions verify the account before `approval()` resolves. Users may see separate pairing and verification requests in MetaMask. Each function returns one wallet account for its selected chain.

### Desktop QR code

Render the URI with your QR code library. The user must scan the QR code from MetaMask.

> [tab] EVM
>
> ```typescript theme={"system"}
> import { connectAndVerifyWithMetaMaskUriEvm } from '@dynamic-labs-sdk/evm/metamask';
> import QRCode from 'qrcode';
>
> const { uri, approval } = await connectAndVerifyWithMetaMaskUriEvm();
>
> const qrDataUrl = await QRCode.toDataURL(uri);
> const qrImage = document.getElementById('metamask-qr-code');
>
> if (qrImage instanceof HTMLImageElement) {
>   qrImage.src = qrDataUrl;
> }
>
> const { walletAccounts } = await approval();
> ```

> [tab] Solana
>
> ```typescript theme={"system"}
> import { connectAndVerifyWithMetaMaskUriSolana } from '@dynamic-labs-sdk/solana/metamask';
> import QRCode from 'qrcode';
>
> const { uri, approval } = await connectAndVerifyWithMetaMaskUriSolana();
>
> const qrDataUrl = await QRCode.toDataURL(uri);
> const qrImage = document.getElementById('metamask-qr-code');
>
> if (qrImage instanceof HTMLImageElement) {
>   qrImage.src = qrDataUrl;
> }
>
> const { walletAccounts } = await approval();
> ```

### Mobile web

Call the MetaMask function from the user's click or tap handler. The SDK opens MetaMask:

```typescript theme={"system"}
import { connectAndVerifyWithMetaMaskUriEvm } from '@dynamic-labs-sdk/evm/metamask';

const { approval } = await connectAndVerifyWithMetaMaskUriEvm();
const { walletAccounts } = await approval();
```

Use the corresponding Solana URI function when the user selects a Solana account.

### React Native

React Native uses the same URI pairing functions. Call the function from the user's press handler so the SDK can open MetaMask.

> [tab] Expo
>
> ```typescript theme={"system"}
> import { connectAndVerifyWithMetaMaskUriEvm } from '@dynamic-labs-sdk/evm/metamask';
>
> const { approval } = await connectAndVerifyWithMetaMaskUriEvm();
> const { walletAccounts } = await approval();
> ```

> [tab] Bare React Native
>
> ```typescript theme={"system"}
> import { connectAndVerifyWithMetaMaskUriEvm } from '@dynamic-labs-sdk/evm/metamask';
>
> const { approval } = await connectAndVerifyWithMetaMaskUriEvm();
> const { walletAccounts } = await approval();
> ```

Configure `metadata.nativeLink` and `metadata.universalLink` on your Dynamic client so MetaMask can return the user to your app. See [React Native setup](https://docs.dynamic.xyz/docs/javascript/react-native/setup.md).

## Route MetaMask from a wallet picker

Call `getWalletOptionsCatalogue({ includeMobileOptions: true })` to include MetaMask's mobile and QR routes. A MetaMask entry can contain:

* `withWalletProvider` for the installed MetaMask browser extension.
* `metamaskSdkUri` for MetaMask SDK desktop QR and mobile pairing.
* `inAppBrowser` for opening your dapp inside MetaMask.

```typescript theme={"system"}
import {
  connectAndVerifyWithWalletProvider,
  getWalletOptionsCatalogue,
  isMobile,
} from '@dynamic-labs-sdk/client';
import { connectAndVerifyWithMetaMaskUriEvm } from '@dynamic-labs-sdk/evm/metamask';
import { connectAndVerifyWithMetaMaskUriSolana } from '@dynamic-labs-sdk/solana/metamask';
import QRCode from 'qrcode';

const walletOptions = await getWalletOptionsCatalogue({
  includeMobileOptions: true,
});

const metaMask = walletOptions.find(
  (walletOption) => walletOption.key === 'metamask',
);
const selectedChain = 'EVM';
const connectionOption = metaMask?.connectionOptions.find(
  (option) =>
    option.chain === selectedChain &&
    (option.type === 'withWalletProvider' ||
      option.type === 'metamaskSdkUri'),
);

if (connectionOption?.type === 'withWalletProvider') {
  await connectAndVerifyWithWalletProvider({
    walletProviderKey: connectionOption.walletProviderKey,
  });
}

if (connectionOption?.type === 'metamaskSdkUri') {
  let connection;

  if (connectionOption.chain === 'EVM') {
    connection = await connectAndVerifyWithMetaMaskUriEvm();
  } else {
    connection = await connectAndVerifyWithMetaMaskUriSolana();
  }

  if (!isMobile()) {
    const qrImage = document.getElementById('metamask-qr-code');

    if (qrImage instanceof HTMLImageElement) {
      qrImage.src = await QRCode.toDataURL(connection.uri);
    }
  }

  await connection.approval();
}
```

Use the chain the user selected. A `withWalletProvider` option is the installed MetaMask browser extension, so connect it through the normal wallet provider flow.

See [Build a Wallet Picker](https://docs.dynamic.xyz/docs/javascript/reference/wallets/build-wallet-picker.md) for `inAppBrowser`, installation, and fallback handling.

> [!WARNING]
>
> Do not route a `metamaskSdkUri` option through `connectWithWalletProvider` or
> `connectAndVerifyWithWalletProvider`. It cannot connect through the generic
> provider path and throws `MetaMaskDirectConnectNotSupportedError`. Route it
> through the chain-specific MetaMask URI functions.

## Common pitfalls

### A fresh pairing replaces stale connection state

Each MetaMask URI function clears stale MetaMask pairing state before requesting a new URI. If pairing fails or expires, call the URI function again. Render its new URI only for a desktop QR code. Do not cache and reuse a previous URI.

### User rejection and timeouts

The initial URI function or `approval()` can reject if the user cancels a wallet interaction. The URI function also rejects with `MetaMaskDisplayUriMissingError` if MetaMask does not provide a pairing URI, or `NoAddressFoundError` if the approved session has no account.

Keep the connection UI available so the user can retry, but allow only one attempt at a time. Disable retry while the URI request or `approval()` is pending.

MetaMask transport timeouts are not automatically retried. A timed-out signing or transaction request may still be pending in MetaMask. Avoid immediately sending the same fund-moving request again.

## Related

* [Build a Wallet Picker](https://docs.dynamic.xyz/docs/javascript/reference/wallets/build-wallet-picker.md) shows how to handle every connection option.
* [Get Wallet Options Catalogue](https://docs.dynamic.xyz/docs/javascript/reference/wallets/get-wallet-options-catalogue.md) documents the wallet and connection option types.
* [Authenticate with External Wallets](https://docs.dynamic.xyz/docs/javascript/authentication-methods/external-wallets.md) covers installed wallet providers.
* [Authenticate with WalletConnect](https://docs.dynamic.xyz/docs/javascript/reference/wallets/walletconnect-integration.md) covers non-MetaMask WalletConnect flows.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.