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

  • Wallet Standard (SVM, Sui, Bitcoin, Polkadot): @usebutr/svm, @usebutr/sui, @usebutr/bitcoin, and @usebutr/polkadot lazily import @wallet-standard/app and use 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 ConnectionError so consumers branch on kind instead of regexing messages. See errors.

Feature presence drives capabilities: a Wallet Standard adapter defines signMessage, signTransaction, signIn, and sendTx only when the wallet advertises the underlying feature, so an adapter never claims more than the wallet supports.

Deliberate edges

These are intentional boundaries, not gaps:

  • No RPC. Wallet Standard adapters have no getBalance or getTransactionReceipt: butr hands you a signer and stays out of the RPC business. Wrap your own client (@solana/kit, @mysten/sui, …). EVM adapters read both through the wallet's own EIP-1193 provider.
  • solana:signTransaction is sign-only. butr returns the signed bytes; you broadcast them, because butr ships no RPC. signTransaction exists only when the wallet advertises the feature.
  • No wallet-side account switch. Neither spec has a "use address X" RPC, so the user picks the active account in the wallet's own UI and butr mirrors it via the change event. setAccount(id, account) changes which exposed account butr treats as active; pass it as { account } to sign with it.
  • Features are read once. Discovery builds an adapter from the features the wallet advertises at registration. A wallet that swaps its feature set at runtime keeps the adapter's original methods until the page reloads.
  • 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/wallet-manager.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.