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
| Option | Type | Notes |
|---|---|---|
projectId | string (required) | From Reown Cloud. |
namespaces | Partial<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) => void | Fires when a QR / deep link must be shown. |
id | string | Adapter id base. Default "walletconnect". Suffixed with -<platform> when multi-namespace. |
name | string | Display name. Default "WalletConnect". |
icon | string | Override 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";| Capability | EVM | SVM | Sui | Bitcoin | Notes |
|---|---|---|---|---|---|
signMessage | ✓ | ✓ | ✓ | ✓ | EVM: EIP-191/712; SVM: bytes; Sui: personal message; BTC: BIP-322-ish. |
sendTransaction | ✓ | ✓ | ✓ | ✓ | BTC routes through bip122:signPsbt with broadcast: true. |
signTransaction | ✓ | ✓ | Sui: raw tx bytes; BTC: PSBT round-trip. | ||
switchChain | ✓ | ✓ | ✓ | BTC: switching network ≈ re-pair. | |
requestAccounts | No EIP-2255 equivalent — "more accounts" means re-pairing. | ||||
switchAccount | Pick the account in the mobile wallet. | ||||
subscribe | ✓ | ✓ | ✓ | ✓ | Relayed via session events (accountsChanged, chainChanged, …). |
getBalance | ✓ | EVM: via the relay's eth_getBalance. Others have no RPC contract. | |||
getTransactionReceipt | ✓ | Same. |
- Changing the account set means tearing down the session and re-pairing.
- Session events (
accountsChanged,chainChanged,disconnect) are relayed throughadapter.subscribe()automatically. - Bitcoin RPC spec is unstable. The
bip122namespace 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.