butr
Core concepts

Standards compliance

How butr follows the Wallet Standard, EIP-6963, and EIP-1193 — what is covered, and the deliberate edges.

butr discovers wallets through the published standards only. There is no window.solana poking, no isMetaMask branching, no hardcoded wallet list. Solana goes through the Wallet Standard; EVM goes through EIP-6963 over EIP-1193. Every chain identifier is CAIP-2 (solana:mainnet, eip155:1).

Discovery is spec-only

  • Solana@usebutr/svm lazily imports @wallet-standard/app and uses getWallets(): get() for wallets already present, on('register') for ones that load later, and on('unregister') to drop a wallet whose extension is disabled or removed mid-session.
  • EVM@usebutr/evm adds the eip6963:announceProvider listener before dispatching eip6963:requestProvider, so wallets that announced early aren't missed. Providers are de-duplicated by info.rdns.
  • Legacy fallback — a raw window.ethereum is adopted only after a 150 ms settle window in which no EIP-6963 wallet answered. It suppresses itself the moment a compliant wallet is present, so it never double-lists.

What's covered

CapabilityWallet Standard (SVM)EIP-6963 / EIP-1193 (EVM)
DiscoverygetWallets() get + register + unregisterrequestProvider / announceProvider, dedupe by rdns
Connectstandard:connecteth_requestAccounts
Silent reconnectstandard:connect({ silent: true }) on hydrationeth_accounts probe on hydration
Disconnectstandard:disconnectwallet_revokePermissions (best-effort)
Account / chain eventsstandard:events change (accounts and chains)accountsChanged, chainChanged, connect, disconnect
Sign messagesolana:signMessagepersonal_sign
Send transactionsolana:signAndSendTransactioneth_sendTransaction
Sign-onlysolana:signTransaction (consumer broadcasts)
Sign insolana:signIn (SIWS)
Switch chainper-call chain + local statewallet_switchEthereumChain

Eager hydration asks the wallet to restore already-authorized accounts without prompting — Wallet Standard's silent input, the eth_accounts read on EIP-1193. A wallet that can't reconnect silently fails the restore cleanly instead of popping a connect dialog on page load. Hardware (Ledger) and WalletConnect adapters, which are inherently interactive, reject a silent reconnect rather than show UI.

Connector errors are normalized once, centrally: EIP-1193 codes 4001 (user rejected), -32002 (request pending), and 4100 / 4900 / 4901 (unauthorized / disconnected) map to a typed ConnectionError so consumers branch on kind instead of regexing messages. See errors.

Feature presence drives capabilities: a flag is true only when the wallet actually advertises the underlying feature, so signMessage, signTransaction, signIn, sendTransaction, and subscribe never claim more than the wallet supports.

Deliberate edges

These are intentional boundaries, not gaps:

  • No RPC. getBalance / getTransactionReceipt return placeholders — butr hands you a signer and stays out of the RPC business. Wrap your own client (viem, @solana/kit, …).
  • solana:signTransaction is sign-only. butr returns the signed bytes; you broadcast them, because butr ships no RPC. Gated by capabilities.signTransaction.
  • No silent account switch. Neither spec has a "use address X" RPC, so switchAccount is a no-op on auto adapters — the user picks the active account in the wallet's own UI; butr mirrors it via the change event.
  • Feature changes don't re-derive capabilities. A wallet that swaps its advertised feature set at runtime won't change a connected entry's capabilities (same as EVM); reconnect to pick up the new set.
  • EIP-1193 message / eth_subscribe is a non-goal. Subscription push streams are a consumer concern; butr surfaces the raw provider via getSigner() for that.

Source: packages/svm/src (Wallet Standard), packages/evm/src (EIP-6963 / EIP-1193), packages/core/src/store/hydration.ts (silent reconnect), packages/core/src/types/errors.ts (error mapping). Behaviour is exercised by the package test suites and the runnable apps/demo-with-* demos.

On this page