butr
Guides

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.