Quickstart
A working multi-chain wallet UI in five minutes with WalletManagerProvider.
This walks through the smallest useful app: discover wallets, connect one, show
it, disconnect. It mirrors the demo-vite reference app, which uses the
batteries-included @usebutr/wallets package.
1. Wrap your app in the provider
WalletManagerProvider from @usebutr/react is the single provider. It takes a
config whose sources say where adapters come from. autoDiscovery() from
@usebutr/wallets discovers EVM (EIP-6963), Solana / Sui / Bitcoin (Wallet
Standard), and Polkadot (injectedWeb3) wallets in one source.
storageKeyPrefix namespaces the storage keys.
The provider reads config once, at mount, so define it at module scope:
import type { WalletManagerConfig } from "@usebutr/core";
import { WalletManagerProvider } from "@usebutr/react";
import { autoDiscovery } from "@usebutr/wallets";
import type { ReactNode } from "react";
const config: WalletManagerConfig = {
sources: [autoDiscovery()],
storageKeyPrefix: "butr-demo",
};
const WalletProvider = ({ children }: { children: ReactNode }) => (
<WalletManagerProvider config={config}>{children}</WalletManagerProvider>
);
export { WalletProvider };Mount it above your tree:
import ReactDOM from "react-dom/client";
import { App } from "./app";
import { WalletProvider } from "./wallet-provider";
ReactDOM.createRoot(document.querySelector("#root")!).render(
<WalletProvider>
<App />
</WalletProvider>,
);2. Wait for hydration
butr restores the previous session asynchronously. Gate your UI on
useIsHydrated() so you don't flash a "not connected" state on reload.
import { useIsHydrated } from "@usebutr/react";
const Content = () => {
const isHydrated = useIsHydrated();
if (!isHydrated) return <p>Loading…</p>;
// …
};3. List discovered wallets and connect
useDiscoveredWallets() is the live list of adapters that have announced
themselves. useConnect() returns connect, which takes an adapter id,
plus the state of the latest attempt. connect never throws: a failure lands
in error.
import { useConnect, useConnectedWallets, useDiscoveredWallets } from "@usebutr/react";
const WalletPicker = () => {
const { connect, connectingId, error } = useConnect();
const discovered = useDiscoveredWallets();
const connected = useConnectedWallets();
const available = discovered.filter((d) => !connected.some((c) => c.connector.id === d.id));
return (
<>
<ul>
{available.map((wallet) => (
<li key={wallet.id}>
<button
disabled={connectingId !== null}
type="button"
onClick={() => connect(wallet.id)}
>
Connect {wallet.name} ({wallet.chainPlatform})
</button>
</li>
))}
</ul>
{error ? <p>{error.message}</p> : null}
</>
);
};4. Show connected wallets and disconnect
Actions come from useWalletManager(); its methods are stable references.
import { useConnectedWallets, useWalletManager } from "@usebutr/react";
const Connected = () => {
const wallets = useConnectedWallets();
const { disconnect } = useWalletManager();
return (
<ul>
{wallets.map((wallet) => (
<li key={wallet.connector.id}>
{wallet.connector.name} — {wallet.account.walletAddress}
<button type="button" onClick={() => disconnect(wallet.connector.id)}>
Disconnect
</button>
</li>
))}
</ul>
);
};That is a complete multi-chain connect flow. What else wallet.connector can
do depends on the wallet: signMessage, sendTx, and switchChain exist only
when they work, and getSigner always does. See
capabilities and the guides.
Source: apps/demo-vite in the butr
repository. Run it locally with
pnpm dev --filter=demo-vite (serves on https://usebutr.demo-vite.localhost). Hosted at
demo.usebutr.com.
Next steps
- Core concepts: what the pool, selection, and active wallet mean, and why hydration is asynchronous.
- Multi-chain & switching: connect more than one wallet and switch chains.
- Signing and Transactions.