Skip to main content
Prerequisites: Create and initialize a Dynamic client and add the extension(s) for your chain(s): EVM and/or Solana. The SDK supports WalletConnect for EVM and Solana only. What is WalletConnect? Users connect a wallet from another device (e.g. mobile) by scanning a QR code or opening a deep link. Use it for cross-device or mobile-to-web flows. You build the UI yourself; there is no built-in picker.

1. Add the extension(s)

Add the WalletConnect extension for each chain you support, once at app setup. Client is optional when using a single Dynamic client.
import {
  createDynamicClient,
  initializeClient,
} from '@dynamic-labs-sdk/client';
import { addWalletConnectEvmExtension } from '@dynamic-labs-sdk/evm/wallet-connect';
import { addWalletConnectSolanaExtension } from '@dynamic-labs-sdk/solana/wallet-connect';

const client = createDynamicClient({ environmentId: 'your-environment-id' });
await initializeClient();

await addWalletConnectEvmExtension(client);
await addWalletConnectSolanaExtension(client); // omit if you only use EVM

2. Get the wallet list

Use getWalletConnectCatalog() for names, icons, and deep links (e.g. for a picker or “Open in MetaMask”). To look up one wallet by provider key after a wallet is connected, see When the user must act.
import { getWalletConnectCatalog } from '@dynamic-labs-sdk/client';

const catalog = await getWalletConnectCatalog();
const wallet = catalog.wallets['metamask'];
const deepLink = wallet?.deeplinks?.native ?? wallet?.deeplinks?.universal;
Wallet entry: name, chain (EVM/SOL/BTC), spriteUrl, primaryColor, deeplinks.native, deeplinks.universal, downloadLinks.androidUrl, downloadLinks.iosUrl.

3. Connect a wallet

Every connect flow returns { uri, approval }. You show the URI as a QR code (desktop) or pass it into a deep link (mobile via appendWalletConnectUriToDeepLink); the user approves in their wallet app; then approval() resolves with { walletAccounts }.
ChainConnect onlyConnect and verify
EVMconnectWithWalletConnectEvmconnectAndVerifyWithWalletConnectEvm (one step)
SolanaconnectWithWalletConnectSolanaconnectAndVerifyWithWalletConnectSolana (connect then verify; no single-step auth for non-EVM)
Connect:
import {
  waitForClientInitialized,
  isMobile,
  getWalletConnectCatalog,
} from '@dynamic-labs-sdk/client';
import { connectWithWalletConnectEvm } from '@dynamic-labs-sdk/evm/wallet-connect';
import { appendWalletConnectUriToDeepLink } from '@dynamic-labs-sdk/wallet-connect';
import QRCode from 'qrcode';

async function connect() {
  await waitForClientInitialized();
  const { uri, approval } = await connectWithWalletConnectEvm({
    addToDynamicWalletAccounts: true,
  });

  if (isMobile()) {
    const catalog = await getWalletConnectCatalog();
    const wallet = Object.values(catalog.wallets).find(w => w.chain === 'EVM');
    const base = wallet?.deeplinks?.native ?? wallet?.deeplinks?.universal;
    if (base) {
      window.location.href = appendWalletConnectUriToDeepLink({
        deepLinkUrl: base,
        walletConnectUri: uri,
      });
    }
  } else {
    document.getElementById('qrcode').src = await QRCode.toDataURL(uri);
  }

  const { walletAccounts } = await approval();
  return walletAccounts;
}
Connect and verify: For connect and verify, call connectAndVerifyWithWalletConnectEvm() or connectAndVerifyWithWalletConnectSolana() instead; same uri / approval pattern.
Solana has no single-step WalletConnect authenticate method (only EIP-155 chains support it), so connectAndVerifyWithWalletConnectSolana performs connect then verify in sequence internally.

4. When the user must act in their wallet

After a request (e.g. sign, switch network), the user approves in their wallet app. Listen for walletConnectUserActionRequested and either open the wallet app (mobile) or show a prompt (desktop). Use getWalletConnectCatalogWalletByWalletProviderKey({ walletProviderKey }) to get the wallet’s deep link when you have a connected wallet or the event’s walletProviderKey.
import {
  onEvent,
  isMobile,
  getWalletConnectCatalogWalletByWalletProviderKey,
} from '@dynamic-labs-sdk/client';

onEvent({
  event: 'walletConnectUserActionRequested',
  listener: async ({ walletProviderKey }) => {
    if (isMobile()) {
      const wallet = await getWalletConnectCatalogWalletByWalletProviderKey({
        walletProviderKey,
      });
      const deepLink = wallet?.deeplinks?.native ?? wallet?.deeplinks?.universal;
      if (deepLink) window.open(deepLink, '_blank');
    } else {
      // e.g. toast.info('Please approve the action in your wallet application');
    }
  },
});

5. Account and network changes

Use wallet provider events with onWalletProviderEvent:
import { onWalletProviderEvent } from '@dynamic-labs-sdk/client';

onWalletProviderEvent({
  walletProviderKey: walletAccounts[0].walletProviderKey,
  event: 'accountsChanged',
  callback: ({ addresses }) => console.log('Accounts:', addresses),
});

6. Full flow example

End-to-end: setup, connect (QR on desktop, deep link on mobile), handle user actions, listen to events.
import {
  createDynamicClient,
  initializeClient,
  waitForClientInitialized,
  onEvent,
  isMobile,
  getWalletConnectCatalog,
  getWalletConnectCatalogWalletByWalletProviderKey,
  onWalletProviderEvent,
} from '@dynamic-labs-sdk/client';
import {
  addWalletConnectEvmExtension,
  connectWithWalletConnectEvm,
} from '@dynamic-labs-sdk/evm/wallet-connect';
import { appendWalletConnectUriToDeepLink } from '@dynamic-labs-sdk/wallet-connect';
import QRCode from 'qrcode';

const client = createDynamicClient({ environmentId: 'your-environment-id' });
await initializeClient();
await addWalletConnectEvmExtension(client);

onEvent({
  event: 'walletConnectUserActionRequested',
  listener: async ({ walletProviderKey }) => {
    if (isMobile()) {
      const wallet = await getWalletConnectCatalogWalletByWalletProviderKey({
        walletProviderKey,
      });
      const deepLink = wallet?.deeplinks?.native ?? wallet?.deeplinks?.universal;
      if (deepLink) window.open(deepLink, '_blank');
    } else {
      console.log('Please approve the action in your wallet application');
    }
  },
});

async function connectWallet() {
  await waitForClientInitialized();
  const catalog = await getWalletConnectCatalog();
  const { uri, approval } = await connectWithWalletConnectEvm();

  if (isMobile()) {
    const w = Object.values(catalog.wallets).find((x) => x.name === 'MetaMask');
    const base = w?.deeplinks?.native ?? w?.deeplinks?.universal;
    if (base) {
      window.location.href = appendWalletConnectUriToDeepLink({
        deepLinkUrl: base,
        walletConnectUri: uri,
      });
    }
  } else {
    document.getElementById('qrcode').src = await QRCode.toDataURL(uri);
  }

  const { walletAccounts } = await approval();
  onWalletProviderEvent({
    walletProviderKey: walletAccounts[0].walletProviderKey,
    event: 'accountsChanged',
    callback: ({ addresses }) => console.log('Accounts:', addresses),
  });
}

7. Error handling

Use instanceof with the exported error classes. The following errors can be thrown by the connect functions or by approval():
import { UserRejectedError } from '@dynamic-labs-sdk/client';
import { SessionClosedUnexpectedlyError } from '@dynamic-labs-sdk/wallet-connect';

try {
  const { uri, approval } = await connectWithWalletConnectEvm();
  const { walletAccounts } = await approval();
} catch (error) {
  if (error instanceof UserRejectedError) {
    console.log('User cancelled');
  } else if (error instanceof SessionClosedUnexpectedlyError) {
    console.log('Session expired, try again');
  } else {
    throw error;
  }
}
For recurring connection or signing issues, see Troubleshooting and WalletConnect unsupported chain.
ErrorWhen
UserRejectedErrorUser rejects the connection or signing request in the wallet app.
WalletAccountAlreadyVerifiedError(connectAndVerifyWithWalletConnectSolana only) The wallet account is already verified.
WalletAlreadyLinkedToAnotherUserError(connectAndVerifyWithWalletConnectSolana only) The wallet is linked to another user.
ErrorWhen
ValueMustBeDefinedErrorA required value is missing: project settings, EVM/Solana networks not configured, no URI returned from WalletConnect, session namespace not established, or (connect-and-verify EVM) universal link not set or nonce failed.
ErrorWhen
SessionClosedUnexpectedlyErrorThe WalletConnect session was closed or is no longer valid (e.g. user disconnected, or session expired). Often thrown when using the wallet provider after disconnect or during approval().
If the client or project is misconfigured, getSignClient can throw ValueMustBeDefinedError when: the app name (display name) is not set in the dashboard, or the WalletConnect project ID is not set in the dashboard.

Connection result: { approval: () => Promise<{ walletAccounts }>, uri: string } (same for EVM and Solana).

API reference

ChainFunctionImport
EVMaddWalletConnectEvmExtension@dynamic-labs-sdk/evm/wallet-connect
EVMconnectWithWalletConnectEvm@dynamic-labs-sdk/evm/wallet-connect
EVMconnectAndVerifyWithWalletConnectEvm@dynamic-labs-sdk/evm/wallet-connect
SolanaaddWalletConnectSolanaExtension@dynamic-labs-sdk/solana/wallet-connect
SolanaconnectWithWalletConnectSolana@dynamic-labs-sdk/solana/wallet-connect
SolanaconnectAndVerifyWithWalletConnectSolana@dynamic-labs-sdk/solana/wallet-connect
BothappendWalletConnectUriToDeepLink@dynamic-labs-sdk/wallet-connect
Related