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 |
| Canonical Hana namespace recommended for detection & routing |
Wallet Standard | Wallet-Standard-aware discovery |
| Registry discovery entry id |
| sats-connect compatibility |
| 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-connectlibrary. Pass the provider id'hanaWallet.bitcoin'so calls route to Hana explicitly.sats-connect'srequest()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 routing1. 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 |
|
|
|
|
|
|
|
|
|
|
|
|
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/ thesats-connectpackage.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(orwindow.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 tosats-connect'srequest(method, params, providerId). It routes explicitly to Hana.sendTransferis 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-RPCerror(direct provider). The provider never throws across the page boundary; always check the envelope.
Resources
sats-connect: https://docs.xverse.app/sats-connect (the standard Hana implements)
Wallet Standard: https://github.com/wallet-standard/wallet-standard
Hana website: https://www.hana.money
Install Hana (Chrome): https://chromewebstore.google.com/detail/hana-wallet/jfdlamikmbghhapbgfoogdffldioobgl
Hana docs: https://docs.hanawallet.io/
