Multi-wallet and chain switching
Hold several wallets at once, choose the active one, act on each platform, and switch a wallet's chain.
Several wallets at once
The pool holds every connected wallet. A user can connect MetaMask and Phantom
together; useConnectedWallets() returns both. Mark one the active wallet with
setActive from useWalletManager():
import { useWallet, useWalletManager } from "@usebutr/react";
const active = useWallet();
const { setActive } = useWalletManager();
const isActive = active?.connector.id === wallet.connector.id;
{
isActive ? null : (
<button type="button" onClick={() => setActive(wallet.connector.id)}>
Make active
</button>
);
}See pool, selection, active for why these are separate.
One wallet per platform
When your app acts on several platforms at once, read each platform's selected
wallet. useSelectedWallet is typed to that platform, so its sendTx takes
that platform's transaction with no narrowing:
import { useSelectedWallet } from "@usebutr/react";
const evm = useSelectedWallet("evm"); // ConnectedWallet<"evm"> | undefined
const svm = useSelectedWallet("svm"); // ConnectedWallet<"svm"> | undefinedSwitch which wallet serves a platform with setSelection(platform, id) from
useWalletManager(). useConnectedWalletsByPlatform() groups the pool for a
per-platform picker.
Switch a wallet's chain
wallet.connector.switchChain(chain) takes a ChainBase. Get the right chain
list for the wallet's platform from CHAINS_BY_PLATFORM in @usebutr/core:
import type { ConnectedWallet } from "@usebutr/core";
import { CHAINS_BY_PLATFORM } from "@usebutr/core";
const ChainPicker = ({ wallet }: { wallet: ConnectedWallet }) => {
const chains = CHAINS_BY_PLATFORM[wallet.connector.chainPlatform];
return (
<select
value={wallet.account.chain.id}
onChange={(event) => {
const target = chains.find((chain) => chain.id === event.target.value);
if (target) {
void wallet.connector.switchChain?.(target);
}
}}
>
{chains.map((chain) => (
<option key={chain.id} value={chain.id}>
{chain.name}
</option>
))}
</select>
);
};Render the picker only when the wallet can switch; switchChain exists only
then:
{
wallet.connector.switchChain ? <ChainPicker wallet={wallet} /> : null;
}What switching means depends on the transport:
- EVM wallets switch for real via
wallet_switchEthereumChain, which may prompt the user. - Wallet Standard wallets (Solana, Sui, Bitcoin) have no switch call.
switchChainre-points butr's view of the wallet, and every later call'schaininput, to the new chain. It exists only when the wallet advertises more than one chain. - Injected Bitcoin wallets switch their network when the provider can (Unisat, Xverse).
- Ledger and injected Polkadot adapters have no
switchChain.
Either way the new chain arrives as an accountsChanged event, bridged
automatically into wallet.account.chain. To send one transaction on another
chain without switching the UI, pass chain to sendTx instead; see
Send a transaction.
A single-platform app imports only its own list, such as EVM_CHAINS_LIST from @usebutr/core;
demo-next does exactly this.
Source: ChainPicker in apps/demo-vite/src/app.tsx (uses CHAINS_BY_PLATFORM) and
apps/demo-next/src/app/page.tsx (uses EVM_CHAINS_LIST).
The Wormhole USDC demo connects EVM and Solana wallets together for CCTP bridging on testnets.