@usebutr/react
The provider and every hook, grouped by what they do.
Provider
WalletManagerProvider
import type { WalletManagerConfig } from "@usebutr/core";
import { WalletManagerProvider } from "@usebutr/react";
import { autoDiscovery } from "@usebutr/wallets";
const config: WalletManagerConfig = {
sources: [autoDiscovery()],
storageKeyPrefix: "my-app",
};
export const Providers = ({ children }: { children: React.ReactNode }) => (
<WalletManagerProvider config={config}>{children}</WalletManagerProvider>
);| Prop | Type | Notes |
|---|---|---|
children | React.ReactNode | Required. |
config | WalletManagerConfig | Read once at mount; define it at module scope. Every field is in the core reference. |
initialState | WalletSnapshot | Seeds the manager synchronously, typically from readWalletSnapshot in a Server Component. See Hydration. |
The provider creates one manager per mount, so a server render never shares
wallet state between requests, and starts it in an effect, so nothing runs on
the server. WalletManagerProviderProps is exported.
useWalletManager(): WalletManager
The manager behind the nearest provider. Its actions are stable references,
and getState() reads without subscribing, so they are safe in callbacks and
effect dependencies.
const { disconnect, disconnectAll, requestAccounts, setAccount, setActive, setSelection } =
useWalletManager();See WalletManager for every member.
Connect
useConnect(): UseConnectResult
The connect action with the state of its latest attempt.
| Field | Type | Notes |
|---|---|---|
connect | (connectorId: string) => void | Fire and forget: a failure lands in error and status, never in a rejection. |
connectAsync | (connectorId: string) => Promise<ConnectedWallet> | Resolves the connected wallet; rejects with a ConnectionError. |
status | "idle" | "connecting" | "success" | "error" | The latest attempt. |
error | ConnectionError | null | The latest attempt's failure. |
connectingId | string | null | The wallet the in-flight attempt is for. |
reset | () => void | Clears error and returns status to "idle", e.g. from a dismiss button. |
const { connect, connectingId, error } = useConnect();
return wallets.map((adapter) => (
<button disabled={connectingId !== null} key={adapter.id} onClick={() => connect(adapter.id)}>
{connectingId === adapter.id ? "Connecting…" : adapter.name}
</button>
));Use connectAsync when the next step needs the wallet, such as signing in
right after connecting.
Read state
| Hook | Returns | Notes |
|---|---|---|
useDiscoveredWallets() | ReadonlyArray<WalletAdapter> | Every adapter the sources announced, including fromAdapters ones. One per platform a wallet speaks. |
useConnectedWallets() | ReadonlyArray<ConnectedWallet> | The pool as an array; stable while the pool is unchanged. |
useWallet(connectorId?) | ConnectedWallet | undefined | The active wallet when connectorId is omitted; undefined for null. |
useSelectedWallet(platform) | ConnectedWallet<P> | undefined | The wallet selected for platform, typed to that platform's adapter. |
useAccounts(connectorId?) | ReadonlyArray<Account> | The active wallet's when connectorId is omitted; empty for null. |
useConnectionStatus() | ConnectionStatus | "connected" | "connecting" | "disconnected" | "reconnecting". |
useIsHydrated() | boolean | The start-up hydration pass has finished. Always true when seeded from initialState. |
useIsReconnecting(connectorId?) | boolean | The wallet is still a shadow seeded from initialState; its connect, getAccounts and getSigner reject until this turns false. |
useDiscoveredWalletsByPlatform() | Map<ChainPlatform, Array<WalletAdapter>> | In CHAIN_PLATFORMS order, empty platforms omitted. |
useConnectedWalletsByPlatform() | Map<ChainPlatform, Array<ConnectedWallet>> | Same ordering. |
useWalletState(selector) | T | Any slice of WalletState. |
useWallet, useAccounts and useIsReconnecting read the active wallet
when connectorId is omitted, and no wallet when it is null, so
useWallet(maybeId ?? null) never falls back to the active one. useWallet,
useSelectedWallet and useAccounts re-render only when that entry's
adapter, active account or account list changes.
useSelectedWallet(platform)
Returns ConnectedWallet<P>, so the connector is already narrowed to that
platform's adapter and its transaction type:
const wallet = useSelectedWallet("svm");
const send = async (serializedTx: Uint8Array) => {
if (wallet?.connector.sendTx) {
return wallet.connector.sendTx(serializedTx);
}
};useWalletState(selector)
A custom selector with shallow equality on the result, so returning an inline object or array does not loop:
const { activeConnectorId, isUserDisconnected } = useWalletState((state) => ({
activeConnectorId: state.activeConnectorId,
isUserDisconnected: state.isUserDisconnected,
}));useDiscoveredWalletsByPlatform() and useConnectedWalletsByPlatform()
A multi-chain wallet announces one adapter per platform it speaks, so the flat list repeats a brand once per chain. These bucket it by platform:
const byPlatform = useDiscoveredWalletsByPlatform();
return [...byPlatform].map(([platform, wallets]) => (
<section key={platform}>
<h3>{platform}</h3>
{wallets.map((adapter) => (
<WalletButton adapter={adapter} key={adapter.id} />
))}
</section>
));The result is memoised on the source list. Outside React,
groupByPlatform does the same bucketing.
Async hooks
type AsyncState<T> =
| { data: null; error: null; status: "idle" }
| { data: null; error: null; status: "loading" }
| { data: T; error: null; status: "success" }
| { data: null; error: Error; status: "error" };A result shows only for the wallet that produced it, so switching wallets
never renders the previous wallet's data. error is always an Error; a
wallet that throws a string or a bare object is wrapped, with the original as
its cause.
useSigner(wallet): AsyncState<WalletSigner>
The signer for wallet, the entry from useWallet() or
useSelectedWallet(platform). Narrow data on kind. Stays "idle" without
a wallet and while the wallet is reconnecting.
const wallet = useSelectedWallet("evm");
const signer = useSigner(wallet);
const client = useMemo(
() =>
signer.data?.kind === "eip1193"
? createWalletClient({ transport: custom(signer.data.provider) })
: null,
[signer.data],
);useBalance(wallet, options?): UseBalanceResult
type UseBalanceOptions = {
account?: Account; // the wallet's active account when omitted
token?: string; // an ERC-20 address on EVM; the native asset when omitted
};
type UseBalanceResult = AsyncState<Balance> & { refetch: () => void };const wallet = useSelectedWallet("evm");
const { data, refetch, status } = useBalance(wallet);Reads through wallet.connector.getBalance, and stays "idle" without a
wallet. Of butr's adapters only the
injected and WalletConnect EVM ones define it, so the hook stays "idle" for
Solana, Sui, Bitcoin, Polkadot and Ledger wallets, and while a wallet is
reconnecting. See balances.
Types
WalletManagerProviderProps, UseConnectResult, ConnectionStatus,
AsyncState, UseBalanceOptions, UseBalanceResult.
Source: packages/react/src/index.ts, context.tsx, hooks/state.ts,
hooks/connect.ts, hooks/grouped.ts, hooks/async-resources.ts.