Connect and disconnect
Discover wallets, connect by id with useConnect, read the attempt's status and error, and disconnect.
Discover what's available
useDiscoveredWallets() is the live list of announced adapters, from every
source: discovered wallets, WalletConnect, Ledger. Filter out the ones already
in the pool before rendering a picker:
import { useConnect, useConnectedWallets, useDiscoveredWallets } from "@usebutr/react";
const { connect, connectingId } = useConnect();
const discovered = useDiscoveredWallets();
const connected = useConnectedWallets();
const available = discovered.filter((d) => !connected.some((c) => c.connector.id === d.id));Connect
connect(id) from useConnect() starts an attempt. It never throws: the
outcome lands in the hook's status and error, so it goes straight into
onClick:
{
available.map((wallet) => (
<button
key={wallet.id}
disabled={connectingId !== null}
type="button"
onClick={() => connect(wallet.id)}
>
{connectingId === wallet.id ? "Connecting…" : `Connect ${wallet.name}`} (
{wallet.chainPlatform})
</button>
));
}A successful connect adds the wallet to the pool, makes it the active wallet, and selects it for its platform if it is new; see pool, selection, active.
A single brand can expose more than one adapter (Phantom EVM + Phantom SVM).
useDiscoveredWalletsByPlatform() groups them; the reference apps render one
button per chainPlatform.
When you need the connected wallet as a value, for example to sign right after
connecting, use connectAsync. It resolves the ConnectedWallet and rejects
with a ConnectionError:
const { connectAsync } = useConnect();
const connectAndSign = async (id: string) => {
const wallet = await connectAsync(id);
await wallet.connector.signMessage?.(new TextEncoder().encode("Welcome"), {
account: wallet.account,
});
};Track the attempt
useConnect() also carries the state of the latest attempt:
const { connectingId, error, reset, status } = useConnect();
// status: "idle" | "connecting" | "success" | "error"
// connectingId: the wallet the in-flight attempt is for, or null
// error: ConnectionError | null
{
error ? (
<p>
{error.kind} — {error.message}{" "}
<button type="button" onClick={reset}>
Dismiss
</button>
</p>
) : null;
}reset() clears error and returns status to "idle". A connect that has
not settled after 90 seconds fails with kind: "Timeout"; one that passes
slowConnectThresholdMs first fires onSlowConnect.
That is the attempt, not the connection. For "is a wallet connected at all",
read useConnectionStatus():
const status = useConnectionStatus(); // "connected" | "connecting" | "disconnected" | "reconnecting""reconnecting" means the active wallet was seeded from a server-rendered
snapshot and its live adapter has not landed yet; see
hydration.
Disconnect
Actions come from useWalletManager(). disconnect takes the adapter id:
import { useWalletManager } from "@usebutr/react";
const { disconnect, disconnectAll } = useWalletManager();
<button type="button" onClick={() => disconnect(wallet.connector.id)}>
Disconnect
</button>;Disconnecting one wallet leaves the rest of the pool intact and forgets that
wallet, so it is not restored on the next load. The active wallet and the
platform's selection fall back to another connected wallet. disconnectAll()
disconnects every wallet and forgets every persisted connection. Run any
sign-out logic of your own (clearing auth tokens, say) right after calling it.
Source: apps/demo-vite/src/app.tsx and apps/demo-next/src/app/page.tsx in the butr
repository. Run pnpm dev --filter=demo-vite (https://usebutr.demo-vite.localhost). Hosted at
demo.usebutr.com.