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

# Build a Wallet Picker

> Render every wallet a user can connect to — installed, URI-based (deeplink + QR), in-app browser, and install-only — from a single SDK call.

This guide shows our officially recommended pattern for building a wallet picker that surfaces **every** wallet a user can connect to (or install) through your dapp, in one ordered list.

You'll do the rendering and the click handling. The SDK does the merging, deduplication, and ordering.

**Prerequisites:** A configured Dynamic client. Each connection option this guide covers requires its own client extension — add only what you intend to surface:

| Connection option type             | Required client extension(s)                                          |
| ---------------------------------- | --------------------------------------------------------------------- |
| `withWalletProvider` (installed)   | Your chain extensions (`addEvmExtension`, `addSolanaExtension`, etc.) |
| `walletConnect` (URI deeplink/QR)  | `addWalletConnect<Chain>Extension` for each chain you want to surface |
| `metamaskSdkUri` (URI deeplink/QR) | `addMetaMaskUri<Chain>Extension` for each chain you want to surface   |
| `inAppBrowser`                     | None (just open the URL)                                              |
| Install-only entries               | None                                                                  |

See [Adding extensions](/javascript/reference/adding-extensions) for setup.

***

## 1. Fetch the catalogue

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

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

Each entry is a `WalletOption` with a `name`, `iconUrl`, an ordered `connectionOptions` array, and an optional `installationUrls` bag for wallets the user can install on this device. See the [reference](/javascript/reference/wallets/get-wallet-options-catalogue) for the full shape.

`includeMobileOptions: true` is what makes the catalogue include URI-based connection options (deeplinking and QR code), in-app browser entries, and install links alongside installed providers. Without it, you only get installed wallets.

***

## 2. Decide what to do when a wallet is clicked

Pick the **first** entry of the wallet's `connectionOptions` array — the catalogue already orders it by priority (`withWalletProvider` > URI deeplink > `inAppBrowser`). Switch on its `type` to drive the right flow:

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

async function onWalletClick(walletOption: WalletOption) {
  const [connectionOption] = walletOption.connectionOptions;

  // Install-only wallet — no live connection available
  if (!connectionOption) {
    return renderInstallationInstructions(walletOption);
  }

  switch (connectionOption.type) {
    case 'withWalletProvider':
      return connectInstalledWallet(connectionOption);
    case 'walletConnect':
    case 'metamaskSdkUri':
      return isMobile()
        ? openUriDeeplinkOnMobile(connectionOption)
        : showQrCodeOnDesktop(connectionOption);
    case 'inAppBrowser':
      return openInAppBrowser(connectionOption);
  }
}
```

The next four sections implement each of those handlers.

***

## 3. Connect installed wallets (`withWalletProvider`)

Requires the chain extension for each chain you support (`addEvmExtension`, `addSolanaExtension`, etc.).

The catalogue has already resolved the right wallet provider key for the user's device. Pass it straight into the connect-and-verify flow — Dynamic does the rest.

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

async function connectInstalledWallet(option: WithWalletProviderConnectionOption) {
  await connectAndVerifyWithWalletProvider({
    walletProviderKey: option.walletProviderKey,
  });
}
```

***

## 4. Drive URI-deeplink connections (`walletConnect` and `metamaskSdkUri`)

Requires the matching extension(s):

* `walletConnect` → `addWalletConnect<Chain>Extension`
* `metamaskSdkUri` → `addMetaMaskUri<Chain>Extension`

Both URI-deeplink discriminators share the same shape — `deeplinks: { native?, universal? }` plus the `type` — but mint their URI through different SDK functions. Wrap the discrimination in one helper so the rest of your picker doesn't care:

```typescript theme={"system"}
import {
  connectAndVerifyWithMetaMaskUriEvm,
  connectAndVerifyWithMetaMaskUriSolana,
} from '@dynamic-labs-sdk/client';
import {
  connectAndVerifyWithWalletConnectEvm,
} from '@dynamic-labs-sdk/evm/wallet-connect';
import {
  connectAndVerifyWithWalletConnectSolana,
} from '@dynamic-labs-sdk/solana/wallet-connect';

type UriDeeplinkOption = Extract<
  WalletConnectionOption,
  { type: 'walletConnect' | 'metamaskSdkUri' }
>;

async function mintConnectionUri(option: UriDeeplinkOption) {
  if (option.type === 'metamaskSdkUri') {
    return option.chain === 'EVM'
      ? connectAndVerifyWithMetaMaskUriEvm()
      : connectAndVerifyWithMetaMaskUriSolana();
  }
  return option.chain === 'EVM'
    ? connectAndVerifyWithWalletConnectEvm()
    : connectAndVerifyWithWalletConnectSolana();
}
```

Each function returns `{ uri, approval }`:

* `uri` is what the wallet needs to pair with your dapp.
* `approval` is a promise that resolves once the user approves in their wallet.

**UI tip:** when you launch a URI deeplink or QR, instruct the user **which app** they should open or scan from. This is especially important for `metamaskSdkUri`: that URI is the MetaMask SDK's own pairing format, not WalletConnect, so it only resolves inside MetaMask. The `walletOption.name` and `walletOption.iconUrl` from the catalogue make a good "Open in {Name}" prompt.

### Mobile: open the wallet app via deeplink

`appendConnectionUriToDeeplink` is generic across both discriminators — append the minted URI onto the wallet's preferred deeplink and open it.

```typescript theme={"system"}
import { appendConnectionUriToDeeplink } from '@dynamic-labs-sdk/client';
import { getPreferredWalletDeepLink } from '@dynamic-labs-sdk/wallet-connect';

async function openUriDeeplinkOnMobile(option: UriDeeplinkOption) {
  const { uri, approval } = await mintConnectionUri(option);

  const baseDeeplink = getPreferredWalletDeepLink({ deeplinks: option.deeplinks });
  if (!baseDeeplink) return;

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

  await approval();
}
```

### Desktop: render the URI as a QR code

On desktop, encode the same URI into a QR code so the user can scan it from their mobile wallet:

```typescript theme={"system"}
import QRCode from 'qrcode';

async function showQrCodeOnDesktop(option: UriDeeplinkOption) {
  const { uri, approval } = await mintConnectionUri(option);

  const dataUrl = await QRCode.toDataURL(uri);
  // Render `dataUrl` in your modal / sheet however you usually render images.

  await approval();
}
```

***

## 5. Open in-app browsers (`inAppBrowser`)

No extension required.

Mobile wallets that ship with their own in-app browser expose a URL template containing `{{encodedDappURI}}`. Substitute it with the encoded current URL and open the result — the wallet will load your dapp inside its own browser, where its injected provider is already available.

```typescript theme={"system"}
function openInAppBrowser(option: InAppBrowserConnectionOption) {
  const dappUrl = encodeURIComponent(window.location.href);
  const target = option.url.replace('{{encodedDappURI}}', dappUrl);
  window.open(target, '_blank');
}
```

**UI tip:** as with URI deeplinks, tell the user which wallet they're being handed off to — the in-app browser URL takes them out of your dapp into the wallet's own app, and a "Continue in {walletOption.name}" label removes the surprise.

***

## 6. Show install links for install-only wallets

No extension required.

When a wallet has no `connectionOptions` but has `installationUrls`, render an install affordance. The exact UI is yours — a button, a modal with platform-specific instructions, a pointer to the user's app store, etc.:

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

function renderInstallationInstructions(walletOption: WalletOption) {
  const urls = walletOption.installationUrls;
  if (!urls) return;

  // Pick the right URL for the current platform.
  const target = isMobile()
    ? urls.ios ?? urls.android
    : urls.chrome ?? urls.firefox ?? urls.edge ?? urls.safari ?? urls.opera;

  // Render however fits your UI — this is just a placeholder.
  if (target) window.open(target, '_blank');
}
```

***

## 7. Group installed vs other (optional)

The catalogue's default `'relevance'` ordering already buckets installed wallets first. If you want to render them under separate headings, split by checking whether the first connection option is `withWalletProvider`:

```typescript theme={"system"}
const installed = walletOptions.filter(
  (wallet) => wallet.connectionOptions[0]?.type === 'withWalletProvider',
);
const others = walletOptions.filter(
  (wallet) => wallet.connectionOptions[0]?.type !== 'withWalletProvider',
);
```

***

## See also

* [`getWalletOptionsCatalogue` reference](/javascript/reference/wallets/get-wallet-options-catalogue)
* [Authenticate with WalletConnect](/javascript/reference/wallets/walletconnect-integration) — the lower-level WC-only flow.
* [Get Available Wallets to Connect](/javascript/reference/wallets/get-available-wallets-to-connect) — when you only need installed providers.
