Skip to content

seed-safe/btc-balance

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

btc-balance

Fast Bitcoin address balance lookup library using memory-mapped address_map.bin.

Overview

This library provides instant local lookups for Bitcoin address balances using a memory-mapped binary file built from Bitcoin Core's chainstate database. It's optimized for simplicity and speed when checking small to medium batches of addresses.

Use this library when:

  • You're checking less than ~250k addresses
  • You want simple, straightforward code
  • Startup overhead matters more than per-lookup speed
  • You prefer direct lookups over complex data structures

Performance

For typical wallet scanning (99% miss rate):

Addresses Total Time Per Address Use Case
100 0.05ms 0.5µs Small batches
10,000 5ms 0.5µs Medium batches
1,000,000 500ms 0.5µs Large batch

Lookup speed: ~500ns per address (binary search on mmap'd file)

Project Structure

btc-balance/
├── pkg/btcbalance/          # Library
│   ├── address.go           # Bitcoin address parsing (all types including Taproot)
│   └── balance.go           # Address map reader for balance lookups
├── cmd/btc-balance/         # CLI tool (query + build)
│   └── main.go
├── example.go               # Usage example
├── go.mod
└── README.md

Installation

Install the CLI tool

# Install btc-balance (includes query and build commands)
go install github.com/seed-safe/btc-balance/cmd/btc-balance@latest

Get address_map.bin

Option A: Download pre-built index (fastest)

Note: The index file address_map.bin (2.1 GB) is not included. Download from releases or generate it yourself.

# Create data directory
mkdir -p data

# Download compressed index from releases (1.8 GB compressed, 2.1 GB uncompressed)
curl -L https://github.com/seed-safe/btc-balance/releases/download/v0.1.0/address_map.bin.gz -o data/address_map.bin.gz

# Decompress
gunzip data/address_map.bin.gz

# Verify
btc-balance info

Option B: Build from Bitcoin Core chainstate (most current)

Requires Bitcoin Core with a synced chainstate database:

export BITCOIN_DATADIR=/path/to/bitcoin/datadir
btc-balance build

# This creates data/address_map.bin (~2.1 GB)

Usage

Check index metadata

# View index information (date, block height, number of addresses)
btc-balance info

# Output:
# Address Map Information
# =======================
# Addresses: 56795328
# Created: 2025-10-11 12:53:56 CDT
# Block Hash: ed968cf315...

Query addresses (CLI)

# Query an address (uses data/address_map.bin by default)
btc-balance query --key bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh
# Output: balance_sats=123456

btc-balance query --key 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
# Output: balance_sats=5438000000

btc-balance query --key bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
# Output: no-balance

Use as a library

Installation:

go get github.com/seed-safe/btc-balance

Getting the index file: Before running your code, you need address_map.bin:

  • Production: Download from Releases (1.8 GB compressed, 2.1 GB uncompressed)
    mkdir -p data
    curl -L https://github.com/seed-safe/btc-balance/releases/download/v0.1.0/address_map.bin.gz -o data/address_map.bin.gz
    gunzip data/address_map.bin.gz
  • Testing: Use a sample file or build a subset yourself

Specifying the index location:

// Option 1: Default (checks data/address_map.bin, then ./address_map.bin)
addrMap, _ := btcbalance.OpenAddrMap("")

// Option 2: Explicit path
addrMap, _ := btcbalance.OpenAddrMap("/var/lib/btc-balance/address_map.bin")

// Option 3: Environment variable (best for production)
// export BTC_BALANCE_INDEX=/var/lib/btc-balance/address_map.bin
addrMap, _ := btcbalance.OpenAddrMap("")

Example code:

package main

import (
    "fmt"
    "log"

    "github.com/seed-safe/btc-balance/pkg/btcbalance"
)

func main() {
    // Open address_map.bin (mmap'd, instant startup)
    // Looks for: explicit path > BTC_BALANCE_INDEX env var > ./address_map.bin
    addrMap, err := btcbalance.OpenAddrMap("") // Empty = use default locations
    if err != nil {
        log.Fatal(err)
    }
    defer addrMap.Close()

    // Your address list
    addresses := []string{
        "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
        "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
        "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr", // Taproot
    }

    for _, addr := range addresses {
        // Convert address to scripthash
        scripthash, err := btcbalance.AddressToScripthash(addr)
        if err != nil {
            fmt.Printf("%s: invalid address\n", addr)
            continue
        }

        // Lookup balance
        balance, found := addrMap.Lookup(scripthash)
        if !found || balance == 0 {
            fmt.Printf("%s: no balance\n", addr)
        } else {
            fmt.Printf("%s: %d sats (%.8f BTC)\n", addr, balance, float64(balance)/1e8)
        }
    }
}

Library API

Core Functions

// Address parsing (supports all Bitcoin address types including Taproot)
func AddressToScripthash(address string) ([32]byte, error)
func ScriptFromAddress(address string) ([]byte, error)

// Open address_map.bin (do once at startup)
func OpenAddrMap(path string) (*AddrMapReader, error)

// Get exact balance (fast binary search, ~500ns)
func (r *AddrMapReader) Lookup(key [32]byte) (balance uint64, found bool)

// Close when done
func (r *AddrMapReader) Close() error

Supported Address Types

  • P2PKH (legacy): 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
  • P2SH: 3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX
  • P2WPKH (bech32): bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
  • P2WSH (bech32): bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3
  • P2TR/Taproot (bech32m): bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr
  • Hex scripts: 0x76a914...

Use Cases

  1. Small wallet recoveries - Checking a few dozen to thousands of addresses
  2. Quick balance checks - One-off lookups in scripts
  3. Development/testing - Simple code, easier to understand
  4. Local-first applications - No API dependencies, works offline

Integration Example

Drop-in replacement for Fulcrum API calls

Before (slow):

for _, addr := range addresses {
    balance, err := fulcrumClient.GetBalance(addr)  // 100ms per call!
    if balance > 0 {
        process(addr, balance)
    }
}

After (fast):

addrMap, _ := btcbalance.OpenAddrMap("address_map.bin")
defer addrMap.Close()

for _, addr := range addresses {
    sh, _ := btcbalance.AddressToScripthash(addr)
    balance, found := addrMap.Lookup(sh)  // 0.5µs lookup!
    if found && balance > 0 {
        process(addr, balance)
    }
}

200,000x faster than API calls!

File Format

address_map.bin

Binary file format:

  • Magic: AMAP (0x50414D41)
  • Version: 1
  • Records: Fixed 40-byte records [32]scripthash || uint64 balance
  • Ordering: Sorted by scripthash for binary search
  • Loading: Memory-mapped for instant startup
  • Integrity: CRC32 checksums
  • Metadata: Source block height, creation timestamp

Size: ~2.1 GB (57M unique addresses × 40 bytes)

Development

Building from source

# Clone the repo
git clone https://github.com/seed-safe/btc-balance
cd btc-balance

# Build example (quick test)
go build   # creates ./example binary

# Build CLI tool
go build -o btc-balance ./cmd/btc-balance

# Run tests
go test ./pkg/btcbalance/...

Updating the Index

Rebuild periodically to stay current:

# Stop Bitcoin Core (optional, for consistency)
bitcoin-cli stop

# Rebuild the index
export BITCOIN_DATADIR=/path/to/bitcoin/datadir
./chainstate-scan

# Restart Bitcoin Core
bitcoind -daemon

# This updates address_map.bin with latest UTXO set

Recommended update frequency:

  • Daily for active trading/monitoring
  • Weekly for general use
  • After major balance changes in your address set

Scripthash Format

The library uses Electrum-style scripthashes:

scripthash = SHA256(scriptPubKey)

This is computed automatically by AddressToScripthash() - you just pass in the address string.

Pure Go

  • No cgo dependencies
  • Cross-compiles easily
  • Static binaries
  • Builds with CGO_ENABLED=0

License

MIT

About

Fast Bitcoin address balance lookup library using memory-mapped index

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages