butr
Integrations

bitcoinjs-lib

Derive address formats from network constants, sign messages, and round-trip PSBTs through the wallet.

bitcoinjs-lib is the long-standing Bitcoin transaction toolkit. It is a builder, not an RPC client — it ships network constants and PSBT construction, but no node connection. butr discovers and manages the wallet and exposes signing; bitcoinjs-lib supplies the network definitions and transaction encoding; a separate Esplora/Electrum client (not shown) would supply UTXOs and broadcast.

Derive the address format from the chain

bitcoinjs-lib's networks export carries the bech32 prefix per network. Map the connected chain's CAIP-2 reference to a network to know which address format the wallet should produce:

import { networks } from "bitcoinjs-lib";

const NETWORK_BY_CHAIN_REF: Record<string, typeof networks.bitcoin> = {
  "000000000019d6689c085ae165831e93": networks.bitcoin, // mainnet
  "000000000933ea01ad0ee984209779ba": networks.testnet,
  "0f9188f13cb7b2c71f2a335e3a4fc328": networks.regtest,
};

const ref = wallet.account.chain.id.split(":")[1] ?? "";
const bech32Prefix = NETWORK_BY_CHAIN_REF[ref]?.bech32 ?? "unknown"; // "bc" | "tb" | "bcrt"

Sign a message

signMessage is wired on every Bitcoin adapter — call it on the connector:

const message = new TextEncoder().encode("Hello from butr + bitcoinjs-lib");
const { signature } = await wallet.connector.signMessage(message);
// `signature` is a Uint8Array — render as hex for display.

Sign a PSBT

PSBT signing is optional — not every Bitcoin wallet advertises it. Narrow on chainPlatform so TypeScript exposes signTransaction, then feature-detect at runtime before calling it:

const connector = wallet.connector;
if (connector.chainPlatform !== "bitcoin" || !connector.signTransaction) {
  throw new Error("This wallet does not advertise PSBT signing (bitcoin:signPsbt).");
}

// PSBT bytes — built with bitcoinjs-lib's Psbt class from real UTXOs in
// a production app. The wallet returns the signed PSBT.
const signed = await connector.signTransaction(psbtBytes);

No broadcast. bitcoinjs-lib doesn't talk to a node. signTransaction returns a signed PSBT; you finalize it and POST the raw transaction to an Esplora / Electrum / Bitcoin Core endpoint yourself. The capability flag signTransaction is false on wallets that don't advertise bitcoin:signPsbt — gate the UI on it.

chainPlatform narrowing matters: WalletAdapter's EVM variant has no signTransaction at all. The discriminant teaches TypeScript that the method can exist on a Bitcoin connector; the runtime !connector.signTransaction check still applies because it's optional even on Bitcoin.

Source: apps/demo-with-bitcoin/src/app.tsx in the butr repository. Run pnpm dev --filter=demo-with-bitcoinhttp://localhost:5181.

On this page