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 or Electrum client (not shown) supplies UTXOs and broadcasts.

Get the wallet

useSelectedWallet("bitcoin") types the connector as a BitcoinAdapter, so signTransaction takes PSBT bytes and sendTx a BitcoinTransfer:

import { useSelectedWallet } from "@usebutr/react";

const wallet = useSelectedWallet("bitcoin");

Derive the address format from the chain

bitcoinjs-lib's networks carry the bech32 prefix per network. Map the connected chain to a network to know which address format the wallet uses:

import { BITCOIN_CHAINS } from "@usebutr/core";
import type { Network } from "bitcoinjs-lib";
import { networks } from "bitcoinjs-lib";

const NETWORK_BY_CHAIN = new Map<string, Network>([
  [BITCOIN_CHAINS.mainnet.id, networks.bitcoin],
  [BITCOIN_CHAINS.testnet.id, networks.testnet],
  [BITCOIN_CHAINS.testnet4.id, networks.testnet],
  [BITCOIN_CHAINS.signet.id, networks.testnet], // both share testnet's prefixes
]);

const bech32Prefix = NETWORK_BY_CHAIN.get(wallet.account.chain.id)?.bech32 ?? "unknown";

Sign a message

signMessage exists when the wallet can sign; check it first:

if (!wallet.connector.signMessage) {
  throw new Error(`${wallet.connector.name} cannot sign messages`);
}
const message = new TextEncoder().encode("Hello from butr + bitcoinjs-lib");
const { signature } = await wallet.connector.signMessage(message, { account: wallet.account });

Sign a PSBT

Not every Bitcoin wallet signs PSBTs, so signTransaction is optional: it exists on Wallet Standard wallets that advertise bitcoin:signPsbt and on the injected Unisat and Xverse adapters.

if (!wallet.connector.signTransaction) {
  throw new Error(`${wallet.connector.name} does not sign PSBTs (bitcoin:signPsbt)`);
}
// Built with bitcoinjs-lib's Psbt class from real UTXOs in a production app.
const signed = await wallet.connector.signTransaction(psbt.toBuffer(), {
  account: wallet.account,
});

No broadcast. signTransaction resolves the signed PSBT; finalize it with bitcoinjs-lib and POST the raw transaction to an Esplora, Electrum or Bitcoin Core endpoint yourself. For a plain payment the wallet can broadcast itself, sendTx({ amount, recipient }) resolves the txid, with amount in satoshis.

chain in the options targets one network. Wallet Standard wallets route it per call; Unisat and Xverse switch the whole wallet first, and reject with a ChainMismatch ConnectionError when they cannot. See the Bitcoin connector.

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