Skip to main content
External wallets let users sign up and log in to your app using wallets they already own, such as MetaMask or Phantom. On mobile, these connections happen via WalletConnect, which deep links to the user’s wallet app for approval.
External wallets and embedded wallets are completely compatible. A user can start with an embedded wallet and also link their branded wallet, or vice versa.

Enable External Wallet Login

1

Enable Chains

Enable the chains you want to support in the Chains section of the dashboard. We support Ethereum, all EVM-compatible networks, Solana, and more.
2

Enable Wallet Login

In the Log in & User Profile section of the dashboard, toggle on Wallet Log in under “External Wallets”.
3

Set Up Deep Links

External wallet connections on mobile rely on deep links to redirect users between your app and their wallet app.
Make sure you’ve set up your deep link URLs correctly. See Deeplink URLs.

Multi-Wallet

In the dashboard under External Wallets, you can toggle on Multi-Wallet. When enabled, users can connect more than one wallet to their account and switch between them without signing out. Learn more on the Multi-Wallet page.

How WalletConnect Works on Mobile

When a user connects an external wallet in React Native, the SDK uses WalletConnect under the hood. The flow is:
  1. Your app presents a list of available wallets
  2. The user taps a wallet (e.g. MetaMask)
  3. The SDK generates a WalletConnect URI and deep links to the wallet app
  4. The user approves the connection in their wallet app
  5. The wallet app redirects back to your app with the connection established
This all happens automatically when you use connectWallet — you don’t need to manage WalletConnect sessions directly.

Using Our UI

Once enabled, external wallet login is available by default in the Dynamic UI. Simply show the auth flow and users will see available wallets:
React Native
import { dynamicClient } from '<path to client file>';

// Opens the Dynamic auth modal with wallet options
dynamicClient.ui.auth.show();

Using Your UI

Fetch Available Wallets

Access the list of available wallets through the walletOptions property. Each option includes metadata about the wallet, including whether it uses WalletConnect.
React Native
import { useReactiveClient } from '@dynamic-labs/react-hooks';
import { dynamicClient } from '<path to client file>';

export const useDynamicClient = () => useReactiveClient(dynamicClient);

const WalletList = () => {
  const client = useDynamicClient();
  const walletOptions = client.wallets.walletOptions;

  // Each option contains:
  // - key: unique identifier (e.g. 'metamask', 'phantom')
  // - name: display name
  // - chain: blockchain (e.g. 'EVM', 'SOL')
  // - isWalletConnect: whether it connects via WalletConnect
  // - metadata: { id, name, icon, brandColor, deepLinks }

  return (
    <View>
      {walletOptions
        .filter((option) => option.chain !== null)
        .map((option) => (
          <Text key={`${option.key}-${option.chain}`}>
            {option.name} ({option.chain})
          </Text>
        ))}
    </View>
  );
};

Connect to a Wallet

Use connectWallet with the wallet’s key to initiate the connection. On mobile, this automatically handles the WalletConnect deep link flow — opening the wallet app for approval and returning the connected wallet when done.
React Native
import { useReactiveClient } from '@dynamic-labs/react-hooks';
import { dynamicClient } from '<path to client file>';
import { View, TouchableOpacity, Text, Image, Alert } from 'react-native';

export const useDynamicClient = () => useReactiveClient(dynamicClient);

const WalletSelector = () => {
  const client = useDynamicClient();
  const walletOptions = client.wallets.walletOptions.filter(
    (option) => option.chain !== null
  );

  const handleConnect = async (walletKey: string) => {
    try {
      const wallet = await client.wallets.connectWallet(walletKey);
      console.log('Connected:', wallet.address);
    } catch (error) {
      console.error('Connection failed:', error);
    }
  };

  return (
    <View>
      {walletOptions.map((option) => (
        <TouchableOpacity
          key={`${option.key}-${option.chain}`}
          onPress={() => handleConnect(option.key)}
        >
          {option.metadata.icon && (
            <Image
              source={{ uri: option.metadata.icon }}
              style={{ width: 32, height: 32 }}
            />
          )}
          <Text>{option.name}</Text>
        </TouchableOpacity>
      ))}
    </View>
  );
};

Filter WalletConnect Wallets

You can filter wallets to show only WalletConnect-compatible options, or separate them into categories:
React Native
const client = useDynamicClient();
const walletOptions = client.wallets.walletOptions;

// Only WalletConnect wallets
const wcWallets = walletOptions.filter((w) => w.isWalletConnect);

// Only EVM wallets
const evmWallets = walletOptions.filter((w) => w.chain === 'EVM');

// Only Solana wallets
const solWallets = walletOptions.filter((w) => w.chain === 'SOL');

// Multichain wallets (appear in multiple chains)
const multichainKeys = walletOptions
  .filter((w) => w.group !== null)
  .map((w) => w.key);
You can also filter wallets at the client level using walletsFilter in your client configuration:
React Native
import { createClient } from '@dynamic-labs/client';
import { ReactNativeExtension } from '@dynamic-labs/react-native-extension';

const client = createClient({
  environmentId: 'your-environment-id',
  appOrigin: 'https://your-app.com',
  walletsFilter: (wallets) =>
    wallets.filter((w) => ['metamask', 'phantom', 'rainbow'].includes(w.key)),
})
  .extend(ReactNativeExtension({ appOrigin: 'https://your-app.com' }));

Access Connected Wallets

Once a wallet is connected, access it through userWallets and primary:
React Native
const client = useDynamicClient();

// All connected wallets
const wallets = client.wallets.userWallets;

// Primary wallet
const primaryWallet = client.wallets.primary;

// Set a different wallet as primary
await client.wallets.setPrimary({ walletId: wallet.id });

Wallet Operations

After connecting, you can perform operations on any connected wallet:
React Native
const client = useDynamicClient();
const wallet = client.wallets.primary;

// Sign a message
const { signedMessage } = await client.wallets.signMessage({
  wallet,
  message: 'Hello from Dynamic!',
});

// Get balance
const { balance } = await client.wallets.getBalance({ wallet });

// Get current network
const { network } = await client.wallets.getNetwork({ wallet });

// Switch network (EVM)
await client.wallets.switchNetwork({
  wallet,
  chainId: 137, // Polygon
});

// Send balance
const { hash } = await client.wallets.sendBalance({
  wallet,
  amount: '0.01',
  toAddress: '0x...',
});

Listen for Wallet Events

React to wallet state changes using event listeners:
React Native
const client = useDynamicClient();

// When a new wallet is connected
client.wallets.on('walletAdded', ({ wallet, userWallets }) => {
  console.log('New wallet connected:', wallet.address);
});

// When a wallet is disconnected
client.wallets.on('walletRemoved', ({ wallet }) => {
  console.log('Wallet disconnected:', wallet.address);
});

// When the primary wallet changes
client.wallets.on('primaryChanged', (primary) => {
  console.log('Primary wallet:', primary?.address);
});

// When a message is signed
client.wallets.on('messageSigned', ({ messageToSign, signedMessage }) => {
  console.log('Signed:', signedMessage);
});

Connected vs Authenticated

By default, external wallets use “connect-and-sign” mode, where users both connect their wallet and sign a message to prove ownership. You can also use “connect-only” mode where users just connect without signing. Read about the implications of each mode in Connected vs Authenticated to decide what’s right for your use case.

Further Reading