Pool, selection, and active
Three pieces of state: every connected wallet, the chosen wallet per platform, and the one wallet in focus now.
butr keeps three distinct things. Confusing them is the most common source of bugs, so they are kept separate on purpose.
Pool
Every connected wallet, keyed by adapter id, in connection order.
const wallets = useConnectedWallets(); // the pool as an array
const pool = useWalletState((state) => state.pool); // ReadonlyMap<string, ConnectedWallet>
const phantom = useWallet("wallet-standard:svm-phantom"); // one entryA user can have many wallets in the pool at once (MetaMask + Phantom + WalletConnect).
Selection: per platform
A Map<ChainPlatform, connectorId>: which wallet serves evm, which serves
svm, and so on. The platforms are independent: selecting an EVM wallet does
nothing to SVM.
const evmWallet = useSelectedWallet("evm"); // ConnectedWallet<"evm"> | undefined
const { setSelection } = useWalletManager();
setSelection("evm", "io.metamask");useSelectedWallet(platform) is typed to that platform's adapter, so
evmWallet.connector.sendTx takes an EVM transaction with no narrowing. A
platform with no connected wallet has no selection.
Active: the one in focus
A single global activeConnectorId: the wallet the user is interacting with
right now. This is what most single-wallet UIs read.
const active = useWallet(); // ConnectedWallet | undefined
const { setActive } = useWalletManager();
setActive("wallet-standard:svm-phantom");useWallet, useAccounts, and useIsReconnecting read the active wallet when
you omit the id. Passing null means no wallet, with no fallback to the active
one, which suits an id held in state that may be empty.
How they change
setActive changes only the active wallet, and setSelection changes only
that platform's selection. Both ignore an id that isn't in the pool, and
setSelection ignores a wallet from another platform.
Connecting and disconnecting move them for you, so neither ever points at a wallet that is gone:
connect(id)makes the wallet active. When it is new to the pool, it also becomes the selection for its platform; reconnecting a wallet already in the pool leaves the selection alone.disconnect(id)falls back: the platform's selection moves to another connected wallet of that platform (or goes away), and the active wallet becomes the first one left in the pool (ornull).- Hydration restores the stored active wallet and selection, unless the user already connected or picked a wallet during the restore; their choice wins.
A wallet can be selected for EVM and not be the active wallet at all.
- Single-wallet apps: read
useWallet(), ignore selection. - Multi-chain apps that act on several platforms at once: read
useSelectedWallet("evm")anduseSelectedWallet("svm"). - A "make active" button in a multi-wallet list: call
setActive(id)fromuseWalletManager()(this is exactly what thedemo-vitereference does).
useWalletManager().getState() reads the pool, selection, and active id without subscribing. Use
it inside callbacks and effects to avoid re-renders.
Source: packages/core/src/store/reducer.ts, packages/react/src/hooks/state.ts.