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

> Handle MetaMask connections across desktop (extension), desktop (QR code), and mobile (deeplink) using the JavaScript SDK.

This guide covers every MetaMask connection scenario you'll encounter in a headless integration: desktop with the extension installed, desktop without the extension (QR code), and mobile (deeplink). Each scenario uses a different SDK function, but they all follow the same connect-then-verify pattern.

**Prerequisites:**

* A [configured Dynamic client](/javascript/reference/client/create-dynamic-client) with the [EVM extension](/javascript/reference/evm/adding-evm-extensions) added.
* EVM chains enabled in the [Dynamic dashboard](https://app.dynamic.xyz/dashboard/chains-and-networks).

```typescript theme={"system"}
import { createDynamicClient, initializeClient } from '@dynamic-labs-sdk/client';
import { addEvmExtension } from '@dynamic-labs-sdk/evm';

const client = createDynamicClient({
  environmentId: 'YOUR_ENVIRONMENT_ID',
});

addEvmExtension(client);
await initializeClient();
```

***

## Scenario 1: Desktop — Extension installed

When the MetaMask browser extension is installed, it announces itself via [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) and the SDK registers it as a wallet provider. Connect directly through the provider — no QR code or deeplink needed.

### Detect the extension

Use `getMetaMaskEvmExtensionWalletProviderKey` to check whether MetaMask is available as a browser extension. It returns the wallet provider key (e.g. `'metamaskevm'`) when installed, or `undefined` when not.

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

const extensionKey = getMetaMaskEvmExtensionWalletProviderKey();

if (extensionKey) {
  console.log('MetaMask extension detected:', extensionKey);
} else {
  console.log('MetaMask extension not installed — fall back to QR or deeplink');
}
```

### Connect and verify

Pass the detected key to `connectAndVerifyWithWalletProvider`. This opens the MetaMask extension popup, the user approves the connection, then signs a verification message — all in one call.

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

async function connectMetaMaskExtension() {
  const extensionKey = getMetaMaskEvmExtensionWalletProviderKey();

  if (!extensionKey) {
    throw new Error('MetaMask extension not available');
  }

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

  console.log('Connected:', walletAccount.address);
  return walletAccount;
}
```

If you want to separate connection from verification (e.g. connect first, verify later on a button click):

```typescript theme={"system"}
import {
  connectWithWalletProvider,
  verifyWalletAccount,
} from '@dynamic-labs-sdk/client';

// Step 1: Connect
const walletAccount = await connectWithWalletProvider({
  walletProviderKey: extensionKey,
});

// Step 2: Verify (e.g. on a separate button click)
await verifyWalletAccount({ walletAccount });
```

***

## Scenario 2: Desktop — No extension (QR code)

When MetaMask isn't installed as a browser extension, use the MetaMask SDK URI pairing. This generates a URI that you render as a QR code. The user scans it with the MetaMask mobile app, approves the connection, and your dapp receives the wallet account.

### Connect and verify

`connectAndVerifyWithMetaMaskUriEvm` returns `{ uri, approval }`. Render `uri` as a QR code; `approval()` resolves after the user approves the connection and signs the verification message in their MetaMask mobile app.

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

async function connectMetaMaskQR() {
  const { uri, approval } = await connectAndVerifyWithMetaMaskUriEvm();

  // Render QR code — use any QR library (qrcode, react-qr-code, etc.)
  const qrDataUrl = await QRCode.toDataURL(uri);
  document.getElementById('qr-code').src = qrDataUrl;

  // Wait for user to scan QR and approve in MetaMask mobile
  const { walletAccounts } = await approval();
  console.log('Connected:', walletAccounts[0]?.address);

  return walletAccounts;
}
```

### Connect without verifying

If you want to defer verification, use `connectWithMetaMaskUriEvm` instead:

```typescript theme={"system"}
import { connectWithMetaMaskUriEvm } from '@dynamic-labs-sdk/evm/metamask';
import { verifyWalletAccount } from '@dynamic-labs-sdk/client';
import QRCode from 'qrcode';

async function connectMetaMaskQRThenVerifyLater() {
  const { uri, approval } = await connectWithMetaMaskUriEvm();

  document.getElementById('qr-code').src = await QRCode.toDataURL(uri);

  // Connection only — no verification signature yet
  const { walletAccounts } = await approval();
  const walletAccount = walletAccounts[0];

  // Later, on a separate user action:
  await verifyWalletAccount({ walletAccount });
}
```

### Clear stale sessions

If the user previously connected via QR and the session expired, the MetaMask SDK may spend up to 10 seconds trying to resume it before emitting a fresh URI. Call `clearMetaMaskSessionStorage` before starting a new pairing to skip the resume timeout:

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

await clearMetaMaskSessionStorage();
const { uri, approval } = await connectAndVerifyWithMetaMaskUriEvm();
```

***

## Scenario 3: Mobile — Deeplink

On mobile, you can't show a QR code (the user is already on their phone). Instead, generate the same MetaMask SDK URI and append it to MetaMask's deeplink. This opens the MetaMask app directly, where the user approves the connection.

### Connect and verify

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

const METAMASK_DEEPLINK = 'https://metamask.app.link/wc';

async function connectMetaMaskMobile() {
  const { uri, approval } = await connectAndVerifyWithMetaMaskUriEvm();

  // Append the pairing URI to MetaMask's deeplink and open it
  const deeplink = appendConnectionUriToDeeplink({
    connectionUri: uri,
    deeplinkUrl: METAMASK_DEEPLINK,
  });

  window.open(deeplink, '_blank');

  // Resolves once the user approves in MetaMask
  const { walletAccounts } = await approval();
  console.log('Connected:', walletAccounts[0]?.address);

  return walletAccounts;
}
```

### Get deeplinks from the wallet catalogue

Instead of hardcoding the deeplink URL, you can fetch it from the SDK's wallet catalogue:

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

async function connectMetaMaskMobileWithCatalog() {
  const { uri, approval } = await connectAndVerifyWithMetaMaskUriEvm();

  const catalog = await getWalletConnectCatalog();
  const metamask = catalog.wallets['metamask'];
  const baseDeeplink = metamask?.deeplinks?.native ?? metamask?.deeplinks?.universal;

  if (!baseDeeplink) {
    throw new Error('MetaMask deeplink not found in wallet catalogue');
  }

  window.open(
    appendConnectionUriToDeeplink({ connectionUri: uri, deeplinkUrl: baseDeeplink }),
    '_blank',
  );

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

***

## Putting it all together

Here's a complete example that detects the user's environment and picks the right MetaMask connection flow automatically:

```typescript theme={"system"}
import {
  createDynamicClient,
  initializeClient,
  waitForClientInitialized,
  isMobile,
  connectAndVerifyWithWalletProvider,
  getWalletConnectCatalog,
  appendConnectionUriToDeeplink,
} from '@dynamic-labs-sdk/client';
import { addEvmExtension } from '@dynamic-labs-sdk/evm';
import {
  getMetaMaskEvmExtensionWalletProviderKey,
  connectAndVerifyWithMetaMaskUriEvm,
} from '@dynamic-labs-sdk/evm/metamask';
import QRCode from 'qrcode';

// --- Setup ---
const client = createDynamicClient({
  environmentId: 'YOUR_ENVIRONMENT_ID',
});

addEvmExtension(client);
await initializeClient();

// --- Connect MetaMask ---
async function connectMetaMask() {
  await waitForClientInitialized();

  // 1. Desktop with extension — connect directly
  const extensionKey = getMetaMaskEvmExtensionWalletProviderKey();

  if (extensionKey && !isMobile()) {
    const walletAccount = await connectAndVerifyWithWalletProvider({
      walletProviderKey: extensionKey,
    });
    return [walletAccount];
  }

  // 2. No extension — use MetaMask SDK URI pairing
  const { uri, approval } = await connectAndVerifyWithMetaMaskUriEvm();

  if (isMobile()) {
    // 2a. Mobile — open MetaMask via deeplink
    const catalog = await getWalletConnectCatalog();
    const metamask = catalog.wallets['metamask'];
    const baseDeeplink = metamask?.deeplinks?.native ?? metamask?.deeplinks?.universal;

    if (baseDeeplink) {
      window.open(
        appendConnectionUriToDeeplink({ connectionUri: uri, deeplinkUrl: baseDeeplink }),
        '_blank',
      );
    }
  } else {
    // 2b. Desktop without extension — show QR code
    const qrDataUrl = await QRCode.toDataURL(uri);
    document.getElementById('qr-code').src = qrDataUrl;
  }

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

***

## Using the Wallet Options Catalogue

For a more automated approach, `getWalletOptionsCatalogue` merges all connection methods (extension, MetaMask SDK URI, WalletConnect, in-app browser) into a single ordered list per wallet. MetaMask's entry will include a `withWalletProvider` option when the extension is installed, plus `metamaskSdkUri` options for QR/deeplink.

```typescript theme={"system"}
import { getWalletOptionsCatalogue, isMobile } from '@dynamic-labs-sdk/client';

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

// Find MetaMask
const metamask = walletOptions.find((w) => w.name === 'MetaMask');

if (metamask) {
  // connectionOptions[0] is the highest-priority method:
  //   - 'withWalletProvider' if the extension is installed
  //   - 'metamaskSdkUri' for QR/deeplink otherwise
  const [best] = metamask.connectionOptions;

  switch (best?.type) {
    case 'withWalletProvider':
      // Extension installed — connect directly
      break;
    case 'metamaskSdkUri':
      // QR code (desktop) or deeplink (mobile)
      break;
    case 'inAppBrowser':
      // Open dapp inside MetaMask's in-app browser
      break;
  }
}
```

See [Build a Wallet Picker](/javascript/reference/wallets/build-wallet-picker) for the full pattern with click handlers.

***

## Related

* [Build a Wallet Picker](/javascript/reference/wallets/build-wallet-picker) — the recommended pattern for rendering all wallets with automatic connection routing.
* [Authenticate with WalletConnect](/javascript/reference/wallets/walletconnect-integration) — cross-device connections via WalletConnect (works with MetaMask and other wallets).
* [Connecting and Verifying a Wallet](/javascript/reference/wallets/connect-and-verify-wallet) — general wallet connection and verification reference.
* [Authenticate with External Wallets](/javascript/authentication-methods/external-wallets) — full external wallet authentication flow.
* [Vite.js MetaMask QR code errors](/overview/troubleshooting/react/vitejs-metamask-qr-code-connection-errors) — troubleshooting CJS import errors in Vite dev server.
