butr
Connectors

Bitcoin Wallet Standard

Wallet Standard discovery for Bitcoin wallets plus injected fallbacks (Unisat, sats-connect), via @usebutr/bitcoin.

@usebutr/bitcoin discovers Bitcoin wallets two ways:

  • Wallet Standard — Phantom, Magic Eden, Leather, OKX (modern), …
  • Injected fallbackwindow.unisat (Unisat, OKX legacy, generic window.btc) and sats-connect (Xverse).

The fallback is opt-in via discoverInjectedBitcoinAdapter and runs when no Wallet Standard wallet announces.

Discovery

autoDiscovery() from @usebutr/wallets handles Bitcoin discovery (both routes) automatically. For a Bitcoin-only setup, compose the two sources:

import { createWalletSource } from "@usebutr/core";
import {
  discoverBitcoinAdapters,
  discoverInjectedBitcoinAdapter,
} from "@usebutr/bitcoin";
import { WalletManagerProvider } from "@usebutr/react";

const discovery = createWalletSource((onAdapter) => {
  const offStandard = discoverBitcoinAdapters(onAdapter);
  const offInjected = discoverInjectedBitcoinAdapter(onAdapter, {});
  return () => {
    offStandard();
    offInjected();
  };
});

<WalletManagerProvider discovery={discovery}>
  {children}
</WalletManagerProvider>

@usebutr/bitcoin lazily imports @wallet-standard/app (an optional peer dependency). Restored Bitcoin wallets can land in pendingIds for a moment during that warmup — see hydration.

Capabilities

Resolved from the wallet's advertised features via resolveBitcoinCapabilities:

  • signMessage — true only if bitcoin:signMessage is advertised. Note this is a Bitcoin Signed Message (BIP-322-ish), not interchangeable with EVM signatures.
  • signTransaction — true via bitcoin:signPsbt. PSBT in, signed PSBT out; the consumer finalises and broadcasts.
  • sendTransaction — true via bitcoin:sendTransfer (one-shot send) or bitcoin:signPsbt with broadcast: true (depending on what the wallet advertises).
  • subscribe — true via the Standard change event.
  • switchChain — limited: switching mainnet ↔ testnet typically means re-connecting in the wallet UI.

Always branch on capabilities.

Working with the signer

getSigner() on a Bitcoin adapter returns the WalletStandardWallet. You call features directly (or bridge into bitcoinjs-lib):

const walletStd = (await wallet.connector.getSigner()) as WalletStandardWallet;
const feature = walletStd.features["bitcoin:signPsbt"];
const account = walletStd.accounts[0];
const [{ psbt: signedPsbt }] = await feature.signPsbt({
  account,
  psbt: unsignedPsbt, // base64
});

See the demo-with-bitcoin app for the full build-and-broadcast flow with bitcoinjs-lib.

Chains

BITCOIN_CHAINS_LIST / BITCOIN_CHAINS ship mainnet, testnet, and regtest (keyed by CAIP-2 — bip122:<genesis block hash>). slugify, resolveBitcoinCapabilities, and GENERIC_BITCOIN_ICON are exported for adapter authors.

Address formats vary by wallet. Phantom advertises native SegWit (bech32) only; Xverse and Unisat expose multiple address formats (payment vs ordinal). Read account.walletAddress and detect the format from its prefix.

Source: packages/bitcoin/src (wallet-standard.ts, injected/, capabilities.ts). Used by apps/demo-with-bitcoin and (via @usebutr/wallets) apps/demo-vite.

On this page