Bitcoin Wallet
The UTXOS Bitcoin wallet provides address management, UTXO handling, transfers, PSBT signing, and message signing. The API follows the Xverse Wallet specification for compatibility with existing Bitcoin tooling.
TLDR: Enable a Bitcoin wallet and get your address.
import { Web3Wallet } from "@utxos/sdk";
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0, // 0 = testnet, 1 = mainnet
});
const addresses = await wallet.bitcoin.getAddresses();
console.log("Address:", addresses[0].address);Table of Contents
- Prerequisites
- Setup
- Address Management
- Balance and UTXOs
- Transfers
- PSBT Operations
- Message Signing
- API Reference
- Complete Examples
- Error Handling
- Data Provider Setup
- Security
Prerequisites
You need the following before integrating the Bitcoin wallet:
- UTXOS project ID from utxos.dev/dashboard
- Optional: Blockstream Esplora access for on-chain queries (no API key required)
Setup
Install Dependencies
npm install @utxos/sdk @meshsdk/bitcoin @meshsdk/providerConfigure Environment
# .env
NEXT_PUBLIC_UTXOS_PROJECT_ID=your_project_id
NEXT_PUBLIC_NETWORK_ID=0 # 0 = testnet, 1 = mainnetInitialize the Wallet
import { Web3Wallet, EnableWeb3WalletOptions } from "@utxos/sdk";
import { BlockstreamBitcoinProvider } from "@meshsdk/provider";
async function initBitcoinWallet() {
// Optional: Configure provider via proxy (same-origin Esplora access)
const bitcoinProvider = new BlockstreamBitcoinProvider("testnet");
(bitcoinProvider as { baseUrl: string }).baseUrl = "/api/blockstream/testnet";
const options: EnableWeb3WalletOptions = {
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: parseInt(process.env.NEXT_PUBLIC_NETWORK_ID) || 0,
bitcoinProvider, // Optional: enables enhanced functionality
};
const wallet = await Web3Wallet.enable(options);
return wallet.bitcoin;
}Verify Connection
const bitcoin = await initBitcoinWallet();
const addresses = await bitcoin.getAddresses();
console.log("Address:", addresses[0].address);
const balance = await bitcoin.getBalance();
console.log("Balance:", balance.total, "sats");Address Management
Get wallet addresses with optional filtering by purpose.
// Get all addresses
const addresses = await bitcoin.getAddresses();
// Get specific address types
const filtered = await bitcoin.getAddresses(
["payment", "ordinals"],
"Please provide your addresses",
);
addresses.forEach((addr) => {
console.log("Address:", addr.address);
console.log("Public Key:", addr.publicKey);
console.log("Purpose:", addr.purpose); // "payment" | "ordinals"
console.log("Type:", addr.addressType); // "p2wpkh" | "p2tr" | "p2sh"
console.log("Network:", addr.network); // "mainnet" | "testnet" | "regtest"
});
// Get wallet public key
const publicKey = bitcoin.getPublicKey();
// Get network ID
const networkId = bitcoin.getNetworkId(); // 0 = testnet, 1 = mainnet, 2 = regtestAddress Types
| Type | Format | Prefix (Mainnet) | Prefix (Testnet) |
|---|---|---|---|
| Native SegWit | p2wpkh | bc1q | tb1q |
| Taproot | p2tr | bc1p | tb1p |
| Wrapped SegWit | p2sh | 3 | 2 |
Balance and UTXOs
Query wallet balance and unspent transaction outputs.
// Get balance
const balance = await bitcoin.getBalance();
console.log("Confirmed:", balance.confirmed, "sats");
console.log("Unconfirmed:", balance.unconfirmed, "sats");
console.log("Total:", balance.total, "sats");
// Convert to BTC
const btc = parseInt(balance.total) / 100_000_000;
console.log("Total:", btc, "BTC");
// Get UTXOs
const utxos = await bitcoin.getUTxOs();
utxos.forEach((utxo) => {
console.log("TXID:", utxo.txid);
console.log("Vout:", utxo.vout);
console.log("Value:", utxo.value, "sats");
console.log("Script:", utxo.script);
});
// Calculate total UTXO value
const totalValue = utxos.reduce((sum, u) => sum + u.value, 0);
console.log("Total UTXO Value:", totalValue, "sats");Transfers
Send Bitcoin to one or more recipients.
const result = await bitcoin.sendTransfer({
recipients: [
{ address: "tb1q...", amount: 100000 }, // 100,000 sats
{ address: "tb1p...", amount: 50000 }, // 50,000 sats
],
});
console.log("Transaction ID:", result.txid);Transfer Parameters
| Parameter | Type | Description |
|---|---|---|
| recipients | Array | List of recipient objects |
| recipients[].address | string | Bitcoin address |
| recipients[].amount | number | Amount in satoshis |
PSBT Operations
Sign Partially Signed Bitcoin Transactions for advanced use cases.
const signResult = await bitcoin.signPsbt({
psbt: "cHNidP8BAF4CAAAAAe...", // Base64-encoded PSBT
signInputs: {
"tb1q...": [0, 1], // Address -> input indices to sign
},
broadcast: false, // Set true to broadcast after signing
});
console.log("Signed PSBT:", signResult.psbt);
if (signResult.txid) {
console.log("Broadcast TXID:", signResult.txid);
}PSBT Parameters
| Parameter | Type | Description |
|---|---|---|
| psbt | string | Base64-encoded PSBT |
| signInputs | object | Map of address to input indices |
| broadcast | boolean | Broadcast after signing (default: false) |
Message Signing
Sign arbitrary messages for authentication or verification.
const addresses = await bitcoin.getAddresses();
const signResult = await bitcoin.signMessage({
address: addresses[0].address,
message: "Sign this message to authenticate",
protocol: "ECDSA",
});
console.log("Signature:", signResult.signature);
console.log("Message Hash:", signResult.messageHash);
console.log("Address:", signResult.address);Sign Message Parameters
| Parameter | Type | Description |
|---|---|---|
| address | string | Address to sign with |
| message | string | Message to sign |
| protocol | string | Signing protocol (“ECDSA”) |
API Reference
Methods
| Method | Returns | Description |
|---|---|---|
getAddresses(purposes?, message?) | Promise<Address[]> | Get wallet addresses |
getPublicKey() | string | Get wallet public key |
getNetworkId() | number | Get current network ID |
getBalance() | Promise<Balance> | Get wallet balance |
getUTxOs() | Promise<UTXO[]> | Get unspent outputs |
sendTransfer(params) | Promise<{txid: string}> | Send Bitcoin |
signPsbt(params) | Promise<SignResult> | Sign a PSBT |
signMessage(params) | Promise<SignResult> | Sign a message |
Types
interface Address {
address: string;
publicKey: string;
purpose: "payment" | "ordinals";
addressType: "p2wpkh" | "p2tr" | "p2sh";
network: "mainnet" | "testnet" | "regtest";
}
interface Balance {
confirmed: string;
unconfirmed: string;
total: string;
}
interface UTXO {
txid: string;
vout: number;
value: number;
script: string;
}Complete Examples
Example 1: Transfer with Balance Check
import { Web3Wallet } from "@utxos/sdk";
async function sendBitcoin(recipientAddress: string, amountSats: number) {
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
});
const bitcoin = wallet.bitcoin;
// Check balance first
const balance = await bitcoin.getBalance();
const totalSats = parseInt(balance.total);
if (totalSats < amountSats + 10000) {
throw new Error(`Insufficient balance: ${totalSats} sats available`);
}
// Send transfer
const result = await bitcoin.sendTransfer({
recipients: [{ address: recipientAddress, amount: amountSats }],
});
console.log("Transaction ID:", result.txid);
return result.txid;
}
// Usage
await sendBitcoin("tb1q9pvjqz5u5sdgpatg3wn0ce4v3seqfrqhp4qd2e", 100000);Example 2: PSBT Workflow
import { Web3Wallet } from "@utxos/sdk";
async function signAndBroadcastPsbt(psbtBase64: string) {
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
});
const bitcoin = wallet.bitcoin;
const addresses = await bitcoin.getAddresses();
const utxos = await bitcoin.getUTxOs();
console.log("Available addresses:", addresses.length);
console.log("Available UTXOs:", utxos.length);
const signResult = await bitcoin.signPsbt({
psbt: psbtBase64,
signInputs: {
[addresses[0].address]: [0],
},
broadcast: true,
});
console.log("Broadcast TXID:", signResult.txid);
return signResult.txid;
}Example 3: Message Authentication
import { Web3Wallet } from "@utxos/sdk";
async function authenticateUser(challenge: string) {
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
});
const bitcoin = wallet.bitcoin;
const addresses = await bitcoin.getAddresses();
const signResult = await bitcoin.signMessage({
address: addresses[0].address,
message: challenge,
protocol: "ECDSA",
});
return {
address: signResult.address,
signature: signResult.signature,
messageHash: signResult.messageHash,
};
}Error Handling
Handle common Bitcoin wallet errors gracefully.
import { ApiError } from "@utxos/sdk";
async function safeBitcoinOperation() {
try {
const result = await bitcoin.sendTransfer({
recipients: [{ address: "tb1q...", amount: 50000 }],
});
return result;
} catch (error) {
if (error.message.includes("Insufficient funds")) {
throw new Error("Not enough Bitcoin for this transaction");
}
if (error.message.includes("Invalid address")) {
throw new Error("The recipient address is invalid");
}
if (error.message.includes("read-only wallet")) {
throw new Error("Cannot sign with an address-only wallet");
}
throw error;
}
}Common Errors
| Error | Cause | Solution |
|---|---|---|
| Insufficient funds | Balance too low | Check balance before sending |
| Invalid address | Malformed address | Validate address format |
| read-only wallet | No signing capability | Use a full wallet, not watch-only |
Set Up a Bitcoin Data Provider
For on-chain queries and transaction broadcast, configure a Blockstream Esplora provider. No API key is required.
The public Blockstream API is sufficient for development and typical production volume. Proxying through your backend avoids browser CORS issues and lets you record submit metrics.
Proxy Route (Next.js Pages Router)
Create pages/api/blockstream/[...slug].ts that forwards to https://blockstream.info/api (mainnet) or https://blockstream.info/testnet/api (testnet). UTXOS ships this proxy in the hosted app.
Use the Proxy
import { BlockstreamBitcoinProvider } from "@meshsdk/provider";
import { Web3Wallet } from "@utxos/sdk";
const bitcoinProvider = new BlockstreamBitcoinProvider("testnet");
(bitcoinProvider as { baseUrl: string }).baseUrl = "/api/blockstream/testnet";
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
bitcoinProvider,
});Security
Follow these security practices for production deployments.
| Practice | Description |
|---|---|
| Validate addresses | Verify address format before sending |
| Use testnet first | Test thoroughly before mainnet deployment |
| Check balances | Verify sufficient funds before transfers |
| Review PSBTs | Inspect PSBT details before signing |
| Monitor fees | Check fee rates to avoid overpaying |
| Handle errors | Implement comprehensive error handling |
Integration Test
Verify your setup is working correctly.
import { Web3Wallet } from "@utxos/sdk";
async function testBitcoinIntegration() {
const wallet = await Web3Wallet.enable({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
networkId: 0,
});
const bitcoin = wallet.bitcoin;
// Test addresses
const addresses = await bitcoin.getAddresses();
console.assert(addresses.length > 0, "No addresses returned");
console.assert(
addresses[0].address.startsWith("tb1"),
"Invalid testnet address"
);
console.log("Addresses: OK");
// Test balance
const balance = await bitcoin.getBalance();
console.assert(typeof balance.total === "string", "Invalid balance format");
console.log("Balance: OK");
// Test UTXOs
const utxos = await bitcoin.getUTxOs();
console.assert(Array.isArray(utxos), "Invalid UTXO format");
console.log("UTXOs: OK");
// Test message signing
const signResult = await bitcoin.signMessage({
address: addresses[0].address,
message: "test",
protocol: "ECDSA",
});
console.assert(typeof signResult.signature === "string", "Invalid signature");
console.log("Message signing: OK");
console.log("All tests passed");
}