butr
Guides

Lifecycle callbacks

onConnect, onConnectError, onDisconnect, onHydrated, onSlowConnect, onStorageError.

WalletManagerConfig exposes lifecycle callbacks. They exist so you wire observability and side effects in one place instead of wrapping every connect call.

import type { WalletManagerConfig } from "@usebutr/core";
import { autoDiscovery } from "@usebutr/wallets";

const config: WalletManagerConfig = {
  onConnect: (wallet, { reconnected }) => {
    track(reconnected ? "wallet_restored" : "wallet_connected", { id: wallet.connector.id });
  },
  onConnectError: (error, connectorId) => reportToSentry(error, { connectorId, kind: error.kind }),
  onDisconnect: (wallet, { byUser }) => {
    track("wallet_disconnected", { byUser, id: wallet.connector.id });
  },
  onHydrated: (outcome) => track("hydrated", outcome),
  onSlowConnect: (connectorId) => showHint(`${connectorId} is taking a while…`),
  onStorageError: (error) => reportToSentry(error),
  slowConnectThresholdMs: 8000,
  sources: [autoDiscovery()],
};
CallbackSignatureFires when
onConnect(wallet: ConnectedWallet, context: { reconnected: boolean }) => voida wallet goes live: a user connect, or a silent restore
onConnectError(error: ConnectionError, connectorId: string) => voida connect attempt fails
onDisconnect(wallet: ConnectedWallet, context: { byUser: boolean }) => voida live wallet leaves the pool
onHydrated(outcome: HydrationOutcome) => voidonce, after the start-up restore pass
onSlowConnect(connectorId: string) => voida connect passes slowConnectThresholdMs without settling
onStorageError(error: Error) => voidloading or saving persisted state fails

The manager reads the config once, when it is created, so define it at module scope. A callback that closes over component state sees only the values from that first render.

Notes

  • onConnect fires for connect() with reconnected: false, and for every silent restore with reconnected: true, including a wallet restored late because its adapter announced after the start-up pass. It is the one place to observe every wallet going live.
  • onConnectError receives the normalised ConnectionError, so you branch on error.kind. It fires for every failed attempt, whichever component started it.
  • onDisconnect has byUser: true for disconnect(id) and disconnectAll(), which fires it once per live wallet. byUser: false means the wallet ended the session itself (locked, extension removed, relay session expired); that entry stays persisted and is retried on the next load. Run sign-out logic, such as clearing auth tokens, after calling disconnectAll().
  • onHydrated is the only place to learn which stored wallets came back, which are still waiting for their adapter, and which failed; see hydration. Without it, failed restores are logged with console.warn.
  • onSlowConnect fires at most once per attempt, and only if the connect hasn't resolved or rejected by the threshold (default 5000 ms). Useful for a "still trying, check your wallet" hint or a slow-path metric. It is a hint, not a timeout: the attempt itself fails with Timeout after 90 seconds.
  • onStorageError is the only signal for otherwise-silent persistence failures. With no callback the manager logs them with console.warn. See persistence.

A callback that throws is caught and logged; it never breaks the manager.

Source: packages/core/src/types/manager.ts (WalletManagerConfig), packages/core/src/store/wallet-manager.ts.

On this page