Skip to main content

Hana Wallet Bitcoin Integration Guide

Written by MJ

This guide shows developers how to integrate Hana Wallet into Bitcoin decentralized applications. Hana's Bitcoin layer implements the sats-connect standard and registers via Wallet Standard, so if your dApp already supports Xverse (or any sats-connect wallet), supporting Hana takes only a few lines.

It mirrors our Hana Wallet EVM Integration Guide detection, core functionality, advanced features, the standards involved, a complete example, and troubleshooting.

Supported today: Bitcoin mainnet · Native SegWit (P2WPKH / bc1q) and Taproot (P2TR / bc1p) addresses.


Detection & Provider Selection

When Hana is installed, its content script injects a sats-connect compatible Bitcoin provider and exposes it through several surfaces (all backed by the same provider instance):

Surface

Use

window.hanaWallet.bitcoin

Canonical Hana namespace recommended for detection & routing

Wallet Standard sats-connect: feature (wallet name "Hana Wallet")

Wallet-Standard-aware discovery

window.wbip_providers (WBIP-004) & window.btc_providers (sats-connect)

Registry discovery entry id hanaWallet.bitcoin

window.XverseProviders.BitcoinProvider

sats-connect compatibility

window.BitcoinProvider

bare-global compatibility

Detect Hana the same way you'd detect it on EVM via the hanaWallet namespace:

function getHanaBitcoinProvider() {
if (typeof window === 'undefined') return null;
// window.hanaWallet.available is set when the Hana extension is present.
if (window.hanaWallet?.available && window.hanaWallet.bitcoin) {
return window.hanaWallet.bitcoin;
}
return null;
}

const isHanaInstalled = !!getHanaBitcoinProvider();

Choosing Hana when multiple wallets are present. Hana also sets the shared window.BitcoinProvider / window.XverseProviders.BitcoinProvider globals for compatibility, so a generic "default provider" lookup may resolve to Hana or another wallet when both are installed. To target Hana unambiguously, detect window.hanaWallet.bitcoin and route calls to the provider id hanaWallet.bitcoin (see below).


Core Functionality

You can integrate Hana two ways. Both call the same provider:

  • Recommended the sats-connect library. Pass the provider id 'hanaWallet.bitcoin' so calls route to Hana explicitly. sats-connect's request() returns { status, result | error }.

  • Direct window.hanaWallet.bitcoin.request(method, params). Returns a JSON-RPC 2.0 envelope { jsonrpc: '2.0', id, result | error }.

import { request } from 'sats-connect';

const HANA = 'hanaWallet.bitcoin'; // provider id for explicit routing

1. Connecting (getAccounts)

Request the user's addresses. purposes selects address types: 'payment' (SegWit / bc1q) and/or 'ordinals' (Taproot / bc1p).

import { AddressPurpose } from 'sats-connect';

async function connect() {
const res = await request(
'getAccounts',
{ purposes: [AddressPurpose.Payment, AddressPurpose.Ordinals], message: 'Connect to MyDApp' },
HANA,
);
if (res.status === 'error') throw new Error(res.error.message);

// res.result: Array<{ address, publicKey, addressType: 'p2wpkh' | 'p2tr', purpose }>
const payment = res.result.find((a) => a.purpose === AddressPurpose.Payment);
const ordinals = res.result.find((a) => a.purpose === AddressPurpose.Ordinals);
return { payment, ordinals };
}

2. Sign a message (BIP-322 or ECDSA)

import { MessageSigningProtocols } from 'sats-connect';

async function signMessage(address, message) {
const res = await request(
'signMessage',
{ address, message, protocol: MessageSigningProtocols.BIP322 }, // or .ECDSA
HANA,
);
if (res.status === 'error') throw new Error(res.error.message);
return res.result.signature; // also returns { address, messageHash }
}

3. Sign a PSBT

async function signPsbt(psbtBase64, signerAddress) {
const res = await request(
'signPsbt',
{
psbt: psbtBase64, // base64-encoded PSBT
signInputs: { [signerAddress]: [0, 1] },// which input indexes to sign per address
broadcast: false, // true to broadcast after finalizing
},
HANA,
);
if (res.status === 'error') throw new Error(res.error.message);
return res.result.psbt; // base64 signed (and optionally finalized) PSBT
}

4. Send Bitcoin (sendTransfer)

Hana builds, signs, and broadcasts the payment and returns the transaction id. amount is in satoshis.

async function sendBitcoin(toAddress, amountSats) {
const res = await request(
'sendTransfer',
{ recipients: [{ address: toAddress, amount: amountSats }] },
HANA,
);
if (res.status === 'error') throw new Error(res.error.message);
return res.result.txid;
}

Method reference

Method

Params

Result

getAccounts

{ purposes: AddressPurpose[], message? }

Array<{ address, publicKey, addressType, purpose }>

signMessage

{ address, message, protocol: 'BIP322' | 'ECDSA' }

{ address, messageHash, signature }

signPsbt

{ psbt, signInputs?, finalize?, broadcast?, allowedSighash? }

{ psbt } (base64)

sendTransfer

{ recipients: [{ address, amount /* sats */ }] }

{ txid }


Advanced Features

Account / connection events

Subscribe to changes via the provider's on('change', …), which fires with the current accounts (returns an unsubscribe function):

const provider = window.hanaWallet.bitcoin;
const unsubscribe = provider.on('change', ({ accounts }) => {
if (accounts.length === 0) handleDisconnect();
else handleAccountsChanged(accounts);
});
// later: unsubscribe();

Wallet Standard

Hana registers a Wallet Standard wallet named "Hana Wallet" advertising the sats-connect: feature (plus standard:connect / standard:disconnect / standard:events) for bitcoin:mainnet. Wallet-Standard-aware connect UIs (e.g. via @wallet-standard/app) will discover Hana automatically.


Standards

  • sats-connect Hana implements the sats-connect v3 provider contract (provider.request(method, params) returning a JSON-RPC 2.0 envelope). Compatible with @secretkeylabs/sats-connect-core / the sats-connect package.

  • Wallet Standard Hana announces a sats-connect: wallet so standards-based discovery works without wallet-specific code.

  • BIP-322 message signing supports BIP-322 (and legacy ECDSA) for both SegWit and Taproot addresses.


Complete Example

import { request, AddressPurpose, MessageSigningProtocols } from 'sats-connect';

const HANA = 'hanaWallet.bitcoin';

function isHanaInstalled() {
return typeof window !== 'undefined' && !!window.hanaWallet?.available && !!window.hanaWallet.bitcoin;
}

async function connectHana() {
if (!isHanaInstalled()) throw new Error('Hana Wallet is not installed');

const res = await request(
'getAccounts',
{ purposes: [AddressPurpose.Payment, AddressPurpose.Ordinals], message: 'Connect to MyDApp' },
HANA,
);
if (res.status === 'error') throw new Error(res.error.message);

const payment = res.result.find((a) => a.purpose === AddressPurpose.Payment);

// React to account changes
window.hanaWallet.bitcoin.on('change', ({ accounts }) => {
console.log('Hana accounts changed:', accounts);
});

return payment; // { address, publicKey, addressType: 'p2wpkh', purpose: 'payment' }
}

async function signInWithBitcoin(address) {
const message = `Sign in to MyDApp\nNonce: ${crypto.randomUUID()}`;
const res = await request(
'signMessage',
{ address, message, protocol: MessageSigningProtocols.BIP322 },
HANA,
);
if (res.status === 'error') throw new Error(res.error.message);
return { message, signature: res.result.signature };
}

async function payInvoice(toAddress, amountSats) {
const res = await request('sendTransfer', { recipients: [{ address: toAddress, amount: amountSats }] }, HANA);
if (res.status === 'error') throw new Error(res.error.message);
return res.result.txid;
}

Troubleshooting & Resources

  • "Wallet not installed" make sure you detect window.hanaWallet?.bitcoin (or window.hanaWallet?.available) rather than only a shared global. The Hana namespace is the reliable signal.

  • Calls reach the wrong wallet when multiple are installed: pass the provider id 'hanaWallet.bitcoin' as the third argument to sats-connect's request(method, params, providerId). It routes explicitly to Hana.

  • sendTransfer is mainnet-only / single-recipient today: testnet and multi-recipient are on our roadmap; reach out if you need them.

  • Errors: every method resolves with { status: 'error', error: { code, message } } (sats-connect) or a JSON-RPC error (direct provider). The provider never throws across the page boundary; always check the envelope.

Resources

Did this answer your question?