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
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:
- Your app presents a list of available wallets
- The user taps a wallet (e.g. MetaMask)
- The SDK generates a WalletConnect URI and deep links to the wallet app
- The user approves the connection in their wallet app
- 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:
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.
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.
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:
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:
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:
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:
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:
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