butr
Connectors

WalletConnect

WalletConnect v2 for EVM, Solana, Sui, and Bitcoin mobile wallets — one Reown project id, one QR scan, multiple namespaces.

@usebutr/walletconnect is a WalletConnect v2 adapter for mobile wallets (Trust, Rainbow, Argent, Zerion, MetaMask Mobile, Phantom Mobile, Slush, Magic Eden, …). It is not browser-injected — you build the adapter once and register it through createConnector.

It ships namespace builders for all four ChainPlatform variants — eip155 (EVM), solana (SVM), sui, and bip122 (Bitcoin) — and a multi-namespace factory so a single pairing can hand back one adapter per platform.

Install

npm install @usebutr/walletconnect @walletconnect/universal-provider

@walletconnect/universal-provider is an optional peer dependency; the adapter dynamically imports it, so it never bloats your bundle unless used.

Build the adapter

import { createWalletConnectAdapters } from "@usebutr/walletconnect";

const wcs = await createWalletConnectAdapters({
  projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID!,
  metadata: { name: "My dapp", url: "https://my-dapp.example" },
  namespaces: {
    evm: ["eip155:1", "eip155:137"],
    svm: ["solana:mainnet"],
    sui: ["sui:mainnet"],
    bitcoin: ["bip122:000000000019d6689c085ae165831e93"],
  },
  onPairingUri: (uri) => setQrUri(uri),
});

createWalletConnectAdapters(options) returns Promise<Array<WalletAdapter>> — one per requested namespace, all sharing the same paired session. When more than one namespace is requested, each adapter's id is suffixed with the platform (walletconnect-evm, walletconnect-svm, …) so they coexist in butr's pool. With a single namespace, the id stays the base options.id ?? "walletconnect".

Pass an empty array for a platform to use the namespace builder's default chains. Omit a key entirely to skip that platform.

Options

OptionTypeNotes
projectIdstring (required)From Reown Cloud.
namespacesPartial<Record<ChainPlatform, ReadonlyArray<string>>>Per-platform CAIP-2 chain map. Omit a key to skip the namespace; empty array uses defaults.
metadata{ name?; url?; description?; icons? }Shown in the mobile wallet during pairing. Some wallets refuse to pair without name and url.
onPairingUri(uri: string) => voidFires when a QR / deep link must be shown.
idstringAdapter id base. Default "walletconnect". Suffixed with -<platform> when multi-namespace.
namestringDisplay name. Default "WalletConnect".
iconstringOverride icon (WALLETCONNECT_DEFAULT_ICON is the fallback).

Register it

The adapter is fully formed but un-paired — pairing happens when butr's runtime calls adapter.connect() (when the user clicks "Connect"). Build the adapter once at module scope, then pass it through the unified provider:

import { WalletManagerProvider } from "@usebutr/react";
import { autoDiscovery } from "@usebutr/wallets";
import { createWalletConnectAdapters } from "@usebutr/walletconnect";

const discovery = autoDiscovery();
const wcs = await createWalletConnectAdapters({
  projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID!,
  namespaces: { evm: ["eip155:1"], svm: ["solana:mainnet"] },
  onPairingUri: (uri) => setQrUri(uri),
});
const extra = new Map(wcs.map((a) => [a.id, a] as const));

<WalletManagerProvider
  discovery={discovery}
  connectors={wcs.map((a) => ({ id: a.id, name: a.name, chainPlatform: a.chainPlatform }))}
  createConnector={(id) => extra.get(id) ?? null}
>
  {children}
</WalletManagerProvider>;

Build the adapter once at app boot, cache it, and re-register on each mount. butr's hydration reconnects through the same session if it's still live on the relay.

Rendering the QR

butr ships no QR renderer. onPairingUri hands you the URI string; you render it (@walletconnect/modal, a qrcode lib, or your own UI). On mobile, forward it to window.location to trigger the OS wallet picker.

Namespace builders

Each platform's RPC shape lives in its own builder. The package exports them so you can compose your own factory if needed:

import {
  KNOWN_NAMESPACES,
  bitcoinNamespace,
  evmNamespace,
  solanaNamespace,
  suiNamespace,
} from "@usebutr/walletconnect";

KNOWN_NAMESPACES is the registry createWalletConnectAdapters dispatches through — keyed by ChainPlatform. Adding a fifth platform is three touches: write a builder under src/namespaces/<platform>.ts, add an entry to KNOWN_NAMESPACES, and re-export it.

Capabilities & caveats

Per-namespace capabilities are fixed once paired. Each builder exposes its own constant:

import {
  WALLETCONNECT_CAPABILITIES, // EVM — single-platform factory uses this
  WALLETCONNECT_BITCOIN_CAPABILITIES,
  WALLETCONNECT_SUI_CAPABILITIES,
  WALLETCONNECT_SVM_CAPABILITIES,
} from "@usebutr/walletconnect";
CapabilityEVMSVMSuiBitcoinNotes
signMessageEVM: EIP-191/712; SVM: bytes; Sui: personal message; BTC: BIP-322-ish.
sendTransactionBTC routes through bip122:signPsbt with broadcast: true.
signTransactionSui: raw tx bytes; BTC: PSBT round-trip.
switchChainBTC: switching network ≈ re-pair.
requestAccountsNo EIP-2255 equivalent — "more accounts" means re-pairing.
switchAccountPick the account in the mobile wallet.
subscribeRelayed via session events (accountsChanged, chainChanged, …).
getBalanceEVM: via the relay's eth_getBalance. Others have no RPC contract.
getTransactionReceiptSame.
  • Changing the account set means tearing down the session and re-pairing.
  • Session events (accountsChanged, chainChanged, disconnect) are relayed through adapter.subscribe() automatically.
  • Bitcoin RPC spec is unstable. The bip122 namespace follows Reown's reference. Method names and response shapes still drift between wallets.

Source: packages/walletconnect/src (adapter.ts, capabilities.ts, namespaces/{evm,svm,sui,bitcoin}.ts) in the butr repository.

On this page