npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

bitcoin-wallet-adapter

v1.15.0

Published

Bitcoin wallet adapter using MUI components

Downloads

25

Readme

🌟 Bitcoin Wallet Adapter

npm version License: MIT npm downloads GitHub stars

A production-ready React library for Bitcoin wallet integration with professional UI/UX, comprehensive error handling, and support for 6+ popular Bitcoin wallets. Built with TypeScript, Redux, and modern React patterns. 🚀

✨ Latest Features

  • 🎨 Professional UI/UX - High-contrast, accessible design with Tailwind CSS
  • Real-time Balance Tracking - Pending transaction awareness and accurate balance display
  • 💸 BTC Payment Integration - Built-in usePayBTC hook with transaction ID capture
  • 🔒 Professional Error Handling - Comprehensive error management with detailed feedback
  • 📱 6+ Wallet Support - Unisat, Xverse, Leather, MagicEden, Phantom, OKX
  • 🌐 Mainnet Ready - Optimized for production Bitcoin mainnet usage
  • 🔌 Auto-disconnect on Network Change - When you switch between mainnet/testnet, any connected wallet is automatically disconnected to prevent cross-network mismatches
  • 🖊️ Sign-before-persist Sessions - Wallet connections are only persisted after a successful BIP-322 signature. Sessions are verified on restore (signature + network + TTL)

🧙‍♂️ Tech Stack

  • ⚛️ Frontend: React 18, TypeScript, Material-UI
  • 🔄 State Management: Redux Toolkit with RTK Query
  • 🎨 Styling: Tailwind CSS, CSS-in-JS support
  • 🔐 Security: Network isolation, error boundaries, input validation
  • 📦 Build: Modern bundling with tree-shaking support

🚀 Quick Start

Installation

npm install bitcoin-wallet-adapter
# or
yarn add bitcoin-wallet-adapter
# or
pnpm add bitcoin-wallet-adapter

Complete Working Example

Here's a complete, production-ready implementation you can copy and use immediately:

import React, { useState } from 'react';
import {
  WalletProvider,
  ConnectMultiButton,
  useWalletAddress,
  useWalletBalance,
  usePayBTC,
  useMessageSign,
  Notification
} from 'bitcoin-wallet-adapter';

// 🎯 Main App Component
function App() {
  return (
    <WalletProvider
      customAuthOptions={{
        network: "mainnet", // Production ready
        appDetails: {
          name: "My Bitcoin App",
          icon: "/logo.png"
        }
      }}
    >
      <div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 p-6">
        <header className="text-center mb-8">
          <h1 className="text-4xl font-bold text-slate-800 mb-4">
            🌟 My Bitcoin Wallet App
          </h1>
          
          {/* 🔌 Connect Button - Always visible for persistence */}
          <ConnectMultiButton
            buttonClassname="bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-xl font-semibold shadow-lg transition-all"
            modalContainerClass="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
            modalContentClass="bg-white rounded-2xl shadow-2xl max-w-md w-full mx-4 p-6"
            supportedWallets={["unisat", "xverse", "leather", "magiceden", "phantom", "okx"]}
          />
        </header>
        
        {/* 📊 Wallet Dashboard */}
        <WalletDashboard />
        
        {/* 💸 Payment Interface */}
        <PaymentInterface />
        
        {/* 🔔 Notifications */}
        <Notification />
      </div>
    </WalletProvider>
  );
}

// 📊 Wallet Dashboard Component  
function WalletDashboard() {
  const walletDetails = useWalletAddress();
  const { balance, btcPrice, refreshBalance, loading } = useWalletBalance();
  
  if (!walletDetails.cardinal) {
    return (
      <div className="max-w-2xl mx-auto">
        <div className="bg-white rounded-xl shadow-lg p-8 text-center">
          <div className="text-6xl mb-4">🔐</div>
          <h2 className="text-2xl font-bold text-slate-800 mb-2">
            Wallet Not Connected
          </h2>
          <p className="text-slate-600">
            Connect your Bitcoin wallet to see your balance and start transacting.
          </p>
        </div>
      </div>
    );
  }
  
  return (
    <div className="max-w-4xl mx-auto mb-8">
      <div className="grid md:grid-cols-2 gap-6">
        {/* 💰 Balance Card */}
        <div className="bg-white rounded-xl shadow-lg p-6">
          <div className="flex items-center justify-between mb-4">
            <h3 className="text-xl font-bold text-slate-800">💰 Balance</h3>
            <button
              onClick={refreshBalance}
              disabled={loading}
              className="text-blue-600 hover:text-blue-700 font-medium disabled:opacity-50"
            >
              {loading ? '⏳' : '🔄'} Refresh
            </button>
          </div>
          
          <div className="space-y-3">
            <div className="flex justify-between items-center">
              <span className="text-slate-600">Confirmed:</span>
              <span className="font-bold text-2xl text-emerald-600">
                {(balance?.confirmed || 0).toFixed(8)} BTC
              </span>
            </div>
            
            <div className="flex justify-between items-center">
              <span className="text-slate-600">USD Value:</span>
              <span className="font-semibold text-slate-800">
                ${((balance?.confirmed || 0) * btcPrice).toFixed(2)}
              </span>
            </div>
            
            {(balance?.unconfirmed || 0) > 0 && (
              <div className="flex justify-between items-center">
                <span className="text-slate-600">Pending:</span>
                <span className="font-medium text-orange-600">
                  {balance.unconfirmed.toFixed(8)} BTC
                </span>
              </div>
            )}
          </div>
        </div>
        
        {/* 🔗 Wallet Info Card */}
        <div className="bg-white rounded-xl shadow-lg p-6">
          <h3 className="text-xl font-bold text-slate-800 mb-4">🔗 Wallet Info</h3>
          
          <div className="space-y-3">
            <div>
              <span className="text-slate-600 text-sm">Connected Wallet:</span>
              <p className="font-bold text-lg text-blue-600">
                {walletDetails.wallet || 'Unknown'}
              </p>
            </div>
            
            <div>
              <span className="text-slate-600 text-sm">Cardinal Address:</span>
              <p className="font-mono text-sm bg-slate-100 p-2 rounded truncate">
                {walletDetails.cardinal}
              </p>
            </div>
            
            {walletDetails.ordinal && (
              <div>
                <span className="text-slate-600 text-sm">Ordinal Address:</span>
                <p className="font-mono text-sm bg-slate-100 p-2 rounded truncate">
                  {walletDetails.ordinal}
                </p>
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

// 💸 Payment Interface Component
function PaymentInterface() {
  const walletDetails = useWalletAddress();
  const { payBTC, loading, error } = usePayBTC();
  const [recipient, setRecipient] = useState(
    localStorage.getItem('btc-recipient') || ''
  );
  const [amount, setAmount] = useState(1000); // satoshis
  const [lastTxId, setLastTxId] = useState<string | null>(null);
  
  if (!walletDetails.cardinal) return null;
  
  const handlePayment = async () => {
    try {
      const result = await payBTC({
        address: recipient,
        amount: amount
      });
      
      // Handle both string (txId) and object responses
      const txId = typeof result === 'string' ? result : result.txid || result.transactionId;
      setLastTxId(txId);
      
      // Save recipient for next time
      localStorage.setItem('btc-recipient', recipient);
      
      alert(`✅ Payment sent! Transaction ID: ${txId}`);
    } catch (err: any) {
      console.error('Payment failed:', err);
      alert(`❌ Payment failed: ${err.message}`);
    }
  };
  
  return (
    <div className="max-w-2xl mx-auto">
      <div className="bg-white rounded-xl shadow-lg p-6">
        <h3 className="text-xl font-bold text-slate-800 mb-6">💸 Send Bitcoin</h3>
        
        <div className="space-y-4">
          <div>
            <label className="block text-slate-700 font-medium mb-2">
              Recipient Address:
            </label>
            <input
              type="text"
              value={recipient}
              onChange={(e) => setRecipient(e.target.value)}
              placeholder="bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
              className="w-full px-4 py-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
            />
          </div>
          
          <div>
            <label className="block text-slate-700 font-medium mb-2">
              Amount (satoshis):
            </label>
            <input
              type="number"
              value={amount}
              onChange={(e) => setAmount(parseInt(e.target.value) || 0)}
              min="546"
              className="w-full px-4 py-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
            />
            <p className="text-sm text-slate-500 mt-1">
              ≈ {(amount / 100000000).toFixed(8)} BTC
            </p>
          </div>
          
          <button
            onClick={handlePayment}
            disabled={loading || !recipient || amount < 546}
            className="w-full bg-emerald-600 hover:bg-emerald-700 disabled:bg-slate-400 text-white py-3 rounded-lg font-semibold transition-colors"
          >
            {loading ? '⏳ Processing...' : '💸 Send Bitcoin'}
          </button>
          
          {error && (
            <div className="bg-red-50 border border-red-200 rounded-lg p-4">
              <p className="text-red-800 font-medium">❌ Error: {error.message}</p>
            </div>
          )}
          
          {lastTxId && (
            <div className="bg-emerald-50 border border-emerald-200 rounded-lg p-4">
              <p className="text-emerald-800 font-medium">✅ Transaction sent!</p>
              <p className="text-sm text-emerald-600 font-mono break-all">
                {lastTxId}
              </p>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

export default App;

🎯 Key Implementation Features

  • 🔌 Persistent Connection: ConnectMultiButton stays visible for wallet persistence after page refresh
  • ⚡ Real-time Balance: Automatic balance updates with pending transaction awareness
  • 💸 Payment Integration: Complete BTC payment flow with transaction ID capture
  • 💾 Local Storage: Remembers recipient addresses between sessions
  • 🎨 Professional UI: High-contrast, accessible design with Tailwind CSS
  • 🔒 Error Handling: Comprehensive error management with user-friendly messages
  • 📱 Multi-wallet Support: Works with Unisat, Xverse, Leather, MagicEden, Phantom, OKX

📋 Latest Project Status (v1.8.2)

Completed Enhancements

  • Demo Application Excellence: Professional UI/UX redesign with high-contrast accessibility
  • Payment System: Full BTC payment integration with transaction ID capture and pending balance awareness
  • Error Handling: Comprehensive error management with user-friendly feedback across all wallet operations
  • Architecture Documentation: Complete technical analysis and network switching constraint documentation
  • Multi-wallet Support: Production-ready support for 6+ major Bitcoin wallets
  • Mainnet Optimization: Optimized for production Bitcoin mainnet usage with proper network isolation

🔍 Architectural Insights

  • Network Management: Network is managed via the library's internal Redux store. Set an initial network with WalletProvider's customAuthOptions.network and change it at runtime by dispatching the setNetwork('mainnet'|'testnet') action or using the useNetwork() hook.
  • Hook Design: All hooks (useWalletBalance, usePayBTC, useMessageSign, useWalletConnect) read the network from Redux and do not accept a network parameter.
  • Module Boundaries: Do not pass network props to components. Use the Redux-managed network only.
  • Security: Proper network isolation prevents cross-network transaction issues

🚀 Production Ready Features

  • Professional Error System: BWAError class with severity levels and detailed error tracking
  • Real-time Balance: Accurate balance display with pending transaction awareness
  • Transaction Management: Complete payment flow with proper transaction ID handling
  • Persistent Sessions: Wallet connections persist across page refreshes
  • Responsive Design: Mobile-friendly interface with Tailwind CSS integration

🛠 Usage

🏛 WalletProvider

Wrap your application with the WalletProvider to enable wallet functionalities:

import { WalletProvider } from "bitcoin-wallet-adapter";

function App() {
  return (
    <WalletProvider customAuthOptions={/* optional auth options */}>
      <YourApp />
    </WalletProvider>
  );
}

WalletProvider Props

| Prop | Type | Description | | ----------------- | -------------------------- | -------------------------------------------------- | | children | ReactNode | The child components to be wrapped by the provider | | customAuthOptions | AuthOptionsArgs (optional) | Custom authentication options |

The AuthOptionsArgs type includes:

interface AuthOptionsArgs {
  manifestPath?: string;
  redirectTo?: string;
  appDetails?: {
    name?: string;
    icon?: string;
  };
}

📝 Note: The appDetails field in customAuthOptions is primarily used by the Leather wallet.

🔗 ConnectMultiButton

Use the ConnectMultiButton component to render a multi-wallet connect button:

import { ConnectMultiButton } from "bitcoin-wallet-adapter";

function WalletConnect() {
  return (
    <ConnectMultiButton
      buttonClassname="custom-button-class"
      modalContainerClass="custom-modal-container"
      modalContentClass="custom-modal-content"
      closeButtonClass="custom-close-button"
      headingClass="custom-heading"
      walletItemClass="custom-wallet-item"
      walletImageClass="custom-wallet-image"
      walletLabelClass="custom-wallet-label"
      InnerMenu={CustomInnerMenu}
      icon="custom-icon.png"
      iconClass="custom-icon-class"
      balance={1000}
      connectionMessage="Please sign this message to authenticate your wallet ownership"
      fractal={true}
      supportedWallets={["unisat", "xverse", "leather"]}
    />
  );
}

ConnectMultiButton Props

| Prop | Type | Description | | ------------------- | ---------------------------------------------- | ------------------------------------------------------------ | | buttonClassname | string (optional) | Custom class for the main button | | modalContainerClass | string (optional) | Custom class for the modal container | | modalContentClass | string (optional) | Custom class for the modal content | | closeButtonClass | string (optional) | Custom class for the close button | | headingClass | string (optional) | Custom class for the modal heading | | walletItemClass | string (optional) | Custom class for each wallet item in the list | | walletImageClass | string (optional) | Custom class for wallet logos | | walletLabelClass | string (optional) | Custom class for wallet labels | | InnerMenu | React.ComponentType (optional) | Custom component for the inner menu when wallet is connected | | icon | string (optional) | Custom logo URL of your application | | iconClass | string (optional) | Custom class for the icon | | balance | number (optional) | Wallet balance to display | | connectionMessage | string (optional) | Custom message for wallet connection/signing authentication | | fractal | boolean (optional) | Show only fractal supporting wallets (Unisat | OKX) | | supportedWallets | string[] (optional) | Array of wallet names to be supported in the dApp | | onSignatureCapture | function (optional) | Callback function to capture wallet signature data after successful connection |

The supportedWallets prop allows you to specify which wallets you want to support in your dApp. Pass an array of wallet names (in lowercase) to filter the available wallets. For example:

supportedWallets={["unisat", "xverse", "leather"]}

This will only show the Unisat, Xverse, and Leather wallet options in the connect modal, even if other wallets are installed in the user's browser.

🧩 Build a Custom Connect Button (Headless)

Use the headless hook useWalletConnect() to design your own connect UI while using our logic.

// What happens in this file:
// - Implements a fully custom connect button using the headless `useWalletConnect` hook
// - Lists available wallets, connects/disconnects, shows basic errors
// - Uses Tailwind classes and includes null checks per best practices

import React from 'react';
import {
  WalletProvider,
  useWalletConnect,
  useWalletAddress,
  useNetwork,
  Notification,
} from 'bitcoin-wallet-adapter';

function CustomConnectButton() {
  const {
    isConnected,
    isLoading,
    error,
    currentWallet,
    availableWallets,
    connect,
    disconnect,
    checkAvailableWallets,
  } = useWalletConnect();

  const walletAddr = useWalletAddress();

  React.useEffect(() => {
    checkAvailableWallets();
  }, [checkAvailableWallets]);

  if (isConnected) {
    return (
      <div className="inline-flex items-center gap-3 bg-white border border-slate-200 rounded-xl px-4 py-2 shadow-sm">
        <div className="text-sm">
          <div className="font-semibold text-slate-800">
            {currentWallet?.wallet || 'Wallet'}
          </div>
          <div className="font-mono text-xs text-slate-500 max-w-[220px] truncate">
            {walletAddr?.cardinal ?? '—'}
          </div>
        </div>
        <button
          onClick={disconnect}
          className="bg-slate-800 hover:bg-slate-900 text-white text-sm font-semibold px-3 py-2 rounded-lg disabled:opacity-50"
        >
          Disconnect
        </button>
      </div>
    );
  }

  return (
    <div className="relative inline-block">
      <button
        onClick={checkAvailableWallets}
        disabled={isLoading}
        className="bg-blue-600 hover:bg-blue-700 text-white text-sm font-semibold px-4 py-2 rounded-lg shadow disabled:opacity-50"
      >
        {isLoading ? 'Checking wallets…' : 'Connect Wallet'}
      </button>

      <div className="mt-2 w-64 bg-white border border-slate-200 rounded-xl shadow p-2">
        {(availableWallets?.length ?? 0) > 0 ? (
          <ul className="space-y-1">
            {availableWallets!.map((w) => (
              <li key={w.connector}>
                <button
                  onClick={() => connect(w.connector)}
                  disabled={isLoading}
                  className="flex items-center gap-2 w-full text-left px-3 py-2 rounded-lg hover:bg-slate-50 disabled:opacity-50"
                >
                  {w.logo && <img src={w.logo} alt={w.label} className="h-5 w-5" />}
                  <span className="text-sm text-slate-800">{w.label}</span>
                </button>
              </li>
            ))}
          </ul>
        ) : (
          <div className="text-sm text-slate-500 px-3 py-2">No supported wallets detected.</div>
        )}
        {error && (
          <div className="mt-2 text-sm text-red-700 bg-red-50 border border-red-200 rounded-lg px-3 py-2">
            {error.message}
          </div>
        )}
      </div>
    </div>
  );
}

function NetworkSwitcher() {
  const { network, setNetwork, toggle } = useNetwork();
  return (
    <div className="inline-flex rounded-lg overflow-hidden border border-slate-200 bg-slate-50">
      <button
        onClick={() => setNetwork('mainnet')}
        className={`px-3 py-2 text-sm font-semibold ${
          network === 'mainnet' ? 'bg-emerald-600 text-white' : 'bg-white text-slate-700'
        }`}
      >
        Mainnet
      </button>
      <button
        onClick={() => setNetwork('testnet')}
        className={`px-3 py-2 text-sm font-semibold border-l border-slate-200 ${
          network === 'testnet' ? 'bg-blue-600 text-white' : 'bg-white text-slate-700'
        }`}
      >
        Testnet
      </button>
      <button onClick={toggle} className="px-3 py-2 text-xs text-blue-600 underline">
        Toggle
      </button>
    </div>
  );
}

export default function App() {
  return (
    <WalletProvider
      customAuthOptions={{
        network: 'mainnet',
        appDetails: { name: 'My App', icon: '/logo.png' },
      }}
    >
      <div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 p-6 space-y-6">
        <div className="flex items-center gap-4">
          <CustomConnectButton />
          <NetworkSwitcher />
        </div>
        <Notification />
      </div>
    </WalletProvider>
  );
}

🔐 Signature Handling & Capture

Wallet connections require a BIP-322 signature. After a successful sign-in:

  • The library persists the session only after signature verification.
  • The signature is stored in Redux and exposed in the headless hook return.

useWalletConnect() now returns signature so you can access it anywhere:

import { useWalletConnect } from 'bitcoin-wallet-adapter';

const { isConnected, signature, currentWallet } = useWalletConnect();

if (isConnected && signature) {
  // send signature to your backend for auth, etc.
}

ConnectMultiButton delegates connection logic to useWalletConnect() and uses the hook’s signature to invoke onSignatureCapture.

Signature Capture Example

<ConnectMultiButton
  onSignatureCapture={(signatureData) => {
    console.log('Signature captured:', signatureData);
    // Process signature data for your application
    // signatureData contains: signature, message, address, wallet, network
  }}
/>

Signature Data Structure

The callback receives a signature data object with the following properties:

interface SignatureData {
  signature: string;    // The BIP-322 signature string
  message: string;      // The signed message
  address: string;      // Bitcoin address used for signing
  wallet: string;       // Wallet type (e.g., "Unisat", "Xverse", "Leather")
  network: string;      // Network used ("mainnet" or "testnet")
}

🔐 Session Security

  • Sessions are saved only after successful signature.
  • On restore, the adapter verifies:
    • Signature matches the saved message and address
    • Address matches the active Redux network
    • Message TTL is fresh (≤ 24h)

🧼 Production Logging

  • Non-error logs are disabled in production throughout the library. Dev-only logs are gated by NODE_ENV !== 'production'.

Security Notes

  • The signature is only captured after successful BIP-322 verification
  • All signatures are verified before the callback is invoked
  • The callback is only called once per successful wallet connection

Custom Sign-In Messages

The connectionMessage prop allows you to customize the message that users sign when connecting their wallet for authentication. This is particularly useful for branding or providing specific context about the sign-in process.

Default Message

If no connectionMessage is provided, a default message is generated that includes:

  • The user's Bitcoin address
  • Welcome text with your application name
  • URI and version information
  • Timestamp

Custom Message Example

<ConnectMultiButton
  connectionMessage="Welcome to MyApp! Please sign this message to verify wallet ownership and authenticate your session."
/>

Security Note

The connection message is used for wallet authentication and signature verification. Ensure your custom message is clear about its purpose and maintains security best practices.

📝 Note: If supportedWallets is not provided, all available wallets will be shown.

🌐 Network Switching (Mainnet/Testnet)

Use the useNetwork() hook to read and change the active network globally. All hooks automatically pick up the change from the Redux store.

import { useNetwork } from 'bitcoin-wallet-adapter';

export function NetworkSwitcher() {
  const { network, setNetwork, toggle } = useNetwork();

  return (
    <div className="inline-flex rounded-lg overflow-hidden border border-slate-200 bg-slate-50">
      <button
        onClick={() => setNetwork('mainnet')}
        className={`px-3 py-2 text-sm font-semibold ${network === 'mainnet' ? 'bg-emerald-600 text-white' : 'bg-white text-slate-700'}`}
      >
        Mainnet
      </button>
      <button
        onClick={() => setNetwork('testnet')}
        className={`px-3 py-2 text-sm font-semibold border-l border-slate-200 ${network === 'testnet' ? 'bg-blue-600 text-white' : 'bg-white text-slate-700'}`}
      >
        Testnet
      </button>
      <button onClick={toggle} className="px-3 py-2 text-xs text-blue-600 underline">
        Toggle
      </button>
    </div>
  );
}

Recommended placement: near the wallet connect UI so users can switch before connecting. No network props are needed anywhere else.

Behavior on Network Change

  • When the active network in Redux changes (via useNetwork().setNetwork, toggle, or initial customAuthOptions.network), the adapter will automatically disconnect the currently connected wallet.
  • This prevents accidental cross-network actions (e.g., signing on testnet with a mainnet session) and ensures a clean, explicit reconnection on the new network.

Testnet Wallet Availability

  • The Connect modal only shows wallets compatible with the active network and detected in the browser. Availability may vary by extension and version.

🔔 Notification

Add the Notification component to your app to display wallet-related notifications:

import { Notification } from "bitcoin-wallet-adapter";

function App() {
  return (
    <div>
      <YourAppContent />
      <Notification />
    </div>
  );
}

🪝 API Reference

Core Hooks

import { 
  useWalletAddress,    // Get wallet connection details
  useWalletBalance,    // Get balance and BTC price  
  useMessageSign,      // Sign messages
  usePayBTC           // Send Bitcoin payments
} from "bitcoin-wallet-adapter";

// Get wallet info
const walletDetails = useWalletAddress();
console.log(walletDetails.cardinal); // Payment address
console.log(walletDetails.wallet);   // Wallet name

// Get balance
const { balance, btcPrice } = useWalletBalance();
console.log(balance.confirmed); // Confirmed BTC

// Sign message
const { signMessage } = useMessageSign();
const signature = await signMessage({
  message: "Hello Bitcoin!",
  address: walletDetails.cardinal,
  wallet: walletDetails.wallet
});

// Send payment
const { payBTC } = usePayBTC();
const txId = await payBTC({
  address: "bc1q...",
  amount: 1000, // satoshis
});

🚨 Professional Error Handling System

Bitcoin Wallet Adapter features a comprehensive, professional-grade error handling system that provides consistent error management, automatic notifications, and detailed error tracking across all wallet operations.

🔧 Core Error Handling Components

BWAError Class

Custom error class with professional error codes, severity levels, and contextual information:

import { throwBWAError, BWAErrorCode, BWAErrorSeverity } from 'bitcoin-wallet-adapter';

// Professional error with all context
throwBWAError(
  BWAErrorCode.WALLET_NOT_CONNECTED,
  "No wallet is currently connected. Please connect a wallet first.",
  {
    severity: BWAErrorSeverity.HIGH,
    context: {
      operation: 'message_signing',
      additionalData: { expectedWallet: 'Unisat' }
    }
  }
);

Error Codes & Severity Levels

Error Codes:

  • WALLET_NOT_CONNECTED - No wallet connected
  • WALLET_NOT_SUPPORTED - Unsupported wallet type
  • TRANSACTION_SIGNING_FAILED - Transaction signing errors
  • MESSAGE_SIGNING_FAILED - Message signing errors
  • SIGNATURE_VERIFICATION_FAILED - Signature verification errors
  • PSBT_INVALID - Invalid PSBT format
  • NETWORK_MISMATCH - Network configuration mismatch
  • VALIDATION_ERROR - Input validation errors
  • CONNECTION_FAILED - Wallet connection failures
  • INSUFFICIENT_BALANCE - Insufficient wallet balance
  • USER_REJECTED - User canceled operation

Severity Levels:

  • LOW - Minor issues, recoverable
  • MEDIUM - Moderate issues requiring attention
  • HIGH - Critical errors requiring immediate action

🛠️ Error Handling Functions

throwBWAError()

Primary function for throwing professional errors with automatic notification dispatch:

import { throwBWAError, BWAErrorCode, BWAErrorSeverity } from 'bitcoin-wallet-adapter';

// Throws error AND triggers snackbar notifications
throwBWAError(
  BWAErrorCode.WALLET_NOT_SUPPORTED, 
  "Phantom wallet does not support this operation",
  {
    severity: BWAErrorSeverity.HIGH,
    context: { 
      operation: 'btc_payment',
      walletType: 'Phantom' 
    }
  }
);

wrapAndThrowError()

Wraps existing errors with professional context:

import { wrapAndThrowError, BWAErrorCode } from 'bitcoin-wallet-adapter';

try {
  await someWalletOperation();
} catch (originalError) {
  wrapAndThrowError(
    originalError,
    BWAErrorCode.TRANSACTION_SIGNING_FAILED,
    "Failed to sign transaction with Unisat wallet",
    { operation: 'transaction_signing', walletType: 'Unisat' }
  );
}

useErrorHandler() Hook

React hook for handling errors with automatic UI notifications:

import { useErrorHandler, BWAErrorCode } from 'bitcoin-wallet-adapter';

function MyComponent() {
  const { errors, clearErrors, hasError } = useErrorHandler({
    onError: (error) => {
      // Automatically dispatches snackbar notifications
      console.log('Error handled:', error.code, error.message);
    },
    filterCodes: [BWAErrorCode.WALLET_NOT_CONNECTED], // Optional filtering
    filterSeverity: [BWAErrorSeverity.HIGH], // Optional severity filtering
    autoClearTimeout: 5000 // Auto-clear errors after 5 seconds
  });

  return (
    <div>
      {hasError(BWAErrorCode.WALLET_NOT_CONNECTED) && (
        <p>Please connect your wallet first!</p>
      )}
    </div>
  );
}

✨ Benefits of Professional Error Handling

  • 🎯 Consistent Experience: All errors follow the same professional format
  • 📱 Automatic Notifications: Errors automatically trigger snackbar notifications
  • 🔍 Rich Context: Detailed error context for debugging and user guidance
  • ⚡ Type Safety: Full TypeScript support with professional error codes
  • 📊 Error Tracking: Built-in error history and analytics
  • 🎛️ Configurable: Filter by codes, severity, auto-clear options
  • ♻️ Recoverable: Errors marked as recoverable allow retry mechanisms

🎨 Seamless Error Integration

Built-in error handling with automatic UI notifications:

import { useErrorHandler } from 'bitcoin-wallet-adapter';

function App() {
  // Automatic error handling with professional snackbar notifications
  useErrorHandler({
    onError: (error) => {
      // Errors automatically appear as styled notifications
      // No additional setup required
    }
  });

  return <YourAppComponents />;
}

👛 Supported Wallets

Bitcoin Wallet Adapter currently supports the following wallets:

  • 🦄 Unisat
  • 🌌 Xverse
  • 🐂 Leather/Hiro
  • 🔮 MagicEden
  • 👻 Phantom
  • 🅾️ OKX

🎨 Headless Hooks (Advanced Usage)

For developers who want complete control over their wallet UI design, we provide headless hooks that separate logic from presentation. These hooks give you all the wallet functionality without any predefined UI components.

🚀 Why Use Headless Hooks?

  • Complete Design Freedom: Build any UI you want
  • 🎯 Logic Separation: Clean separation between wallet logic and presentation
  • 🔧 Maximum Flexibility: Customize every aspect of the user experience
  • 📱 Framework Agnostic: Logic works with any design system or component library

📖 Available Headless Hooks

🔌 useWalletConnect

The core hook for wallet connection management:

import { useWalletConnect } from 'bitcoin-wallet-adapter';

function CustomWalletConnect() {
  const {
    // Connection State
    isConnected,
    isLoading,
    error,
    
    // Wallet Information
    currentWallet,
    lastWallet,
    availableWallets,
    
    // Actions
    connect,
    disconnect,
    
    // Utilities
    checkAvailableWallets,
    refreshBalance
  } = useWalletConnect();

  return (
    <div className="my-custom-design">
      {isConnected ? (
        <div>
          <h3>Connected to {currentWallet?.wallet}</h3>
          <p>Cardinal: {currentWallet?.cardinal}</p>
          <p>Ordinal: {currentWallet?.ordinal}</p>
          <button onClick={disconnect}>Disconnect</button>
        </div>
      ) : (
        <div>
          <h3>Choose a Wallet</h3>
          {availableWallets.map((wallet) => (
            <button
              key={wallet.connector}
              onClick={() => connect(wallet.connector)}
              disabled={isLoading}
            >
              <img src={wallet.logo} alt={wallet.label} />
              {wallet.label}
            </button>
          ))}
          {error && <p>Error: {error.message}</p>}
        </div>
      )}
    </div>
  );
}

💰 useWalletBalance

Manage wallet balance and BTC price:

import { useWalletBalance } from 'bitcoin-wallet-adapter';

function CustomBalanceDisplay() {
  const {
    // Balance State
    balance,
    btcPrice,
    isLoading,
    error,
    
    // Actions
    refreshBalance,
    
    // Utilities
    formatBalance,
    formatPrice
  } = useWalletBalance();

  return (
    <div className="balance-container">
      <div className="balance-card">
        <h4>Wallet Balance</h4>
        {isLoading ? (
          <div>Loading balance...</div>
        ) : (
          <>
            <div className="btc-balance">
              <span>{formatBalance(balance.btc || 0)} BTC</span>
              <small>${formatPrice((balance.btc || 0) * btcPrice)}</small>
            </div>
            <div className="sats-balance">
              <span>{balance.sats?.toLocaleString()} sats</span>
            </div>
          </>
        )}
        <button onClick={refreshBalance} disabled={isLoading}>
          Refresh Balance
        </button>
        {error && <p className="error">{error.message}</p>}
      </div>
    </div>
  );
}

✍️ useWalletSigning

Handle message and transaction signing:

import { useWalletSigning } from 'bitcoin-wallet-adapter';

function CustomSigning() {
  const {
    // Signing State
    isLoading,
    error,
    lastSignature,
    
    // Actions
    signMessage,
    signTransaction,
    signPSBT,
    
    // Utilities
    verifySignature,
    clearError
  } = useWalletSigning();

  const handleSignMessage = async () => {
    try {
      const signature = await signMessage({
        message: "Hello Bitcoin!",
        address: "current-wallet-address"
      });
      console.log("Message signed:", signature);
    } catch (err) {
      console.error("Signing failed:", err);
    }
  };

  const handleSignTransaction = async () => {
    try {
      const result = await signTransaction({
        psbt: "your-psbt-here",
        action: "sell"
      });
      console.log("Transaction signed:", result);
    } catch (err) {
      console.error("Transaction signing failed:", err);
    }
  };

  return (
    <div className="signing-interface">
      <div className="signing-actions">
        <button 
          onClick={handleSignMessage}
          disabled={isLoading}
        >
          {isLoading ? 'Signing...' : 'Sign Message'}
        </button>
        
        <button 
          onClick={handleSignTransaction}
          disabled={isLoading}
        >
          {isLoading ? 'Signing...' : 'Sign Transaction'}
        </button>
      </div>

      {lastSignature && (
        <div className="signature-result">
          <h4>Last Signature:</h4>
          <pre>{JSON.stringify(lastSignature, null, 2)}</pre>
        </div>
      )}

      {error && (
        <div className="error-message">
          <p>Error: {error.message}</p>
          <button onClick={clearError}>Clear Error</button>
        </div>
      )}
    </div>
  );
}

🎨 Building Custom Components

You can combine these headless hooks to create sophisticated wallet interfaces:

import { 
  useWalletConnect, 
  useWalletBalance, 
  useWalletSigning 
} from 'bitcoin-wallet-adapter';

function CompleteCustomWallet() {
  const { isConnected, connect, disconnect, availableWallets, currentWallet } = useWalletConnect();
  const { balance, formatBalance } = useWalletBalance();
  const { signMessage, isLoading: isSigning } = useWalletSigning();

  if (!isConnected) {
    return (
      <div className="connect-interface">
        <h2>Connect Your Bitcoin Wallet</h2>
        <div className="wallet-grid">
          {availableWallets.map((wallet) => (
            <div 
              key={wallet.connector}
              className="wallet-option"
              onClick={() => connect(wallet.connector)}
            >
              <img src={wallet.logo} alt={wallet.label} />
              <span>{wallet.label}</span>
            </div>
          ))}
        </div>
      </div>
    );
  }

  return (
    <div className="wallet-dashboard">
      <header className="wallet-header">
        <h2>{currentWallet?.wallet} Connected</h2>
        <button onClick={disconnect} className="disconnect-btn">
          Disconnect
        </button>
      </header>

      <div className="wallet-content">
        <div className="balance-section">
          <h3>Balance: {formatBalance(balance.btc || 0)} BTC</h3>
          <small>{balance.sats?.toLocaleString()} sats</small>
        </div>

        <div className="actions-section">
          <button 
            onClick={() => signMessage({ 
              message: "Custom message", 
              address: currentWallet?.cardinal || "" 
            })}
            disabled={isSigning}
          >
            Sign Message
          </button>
        </div>
      </div>
    </div>
  );
}

🤝 Contributing

We welcome contributions to the Bitcoin Wallet Adapter! Please read our CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests. Let's build the future of Bitcoin wallets together! 🚀

📄 License

This project is licensed under the MIT License - see the LICENSE.md file for details.

🌐 Connect with Us

X Codepen Twitter

💻 Tech Stack

TypeScript React ![Redux](https://img.shields.io/badge/redux-%23593d88.svg