Get Address Balance

Overview

The library provides methods to query the satoshi balance of one or more Bitcoin addresses. Both single and batch lookups are supported, with an optional filter to exclude ordinal-bearing UTXOs from the reported balance.

Balance Query Flow Address Provider Filter Ordinals? Yes No Spendable Balance Total Balance

Single Address Balance

Use the getBalance() method to retrieve the total satoshi balance for a single Bitcoin address. It accepts the target address and an optional filterOrdinals flag.

When the filterOrdinals flag is set to true, any UTXOs containing ordinal inscriptions are excluded from the balance calculation, ensuring the returned value reflects only the spendable satoshis.

The result is returned as a bigint.

typescript
Single address balance with no ordinals
import { JSONRpcProvider } from 'opnet';
import { networks } from '@btc-vision/bitcoin';

const network = networks.regtest;
const provider = new JSONRpcProvider({ url: 'https://regtest.opnet.org', network });

// Get balance (filters ordinals by default)
const balance = await provider.getBalance('bcrt1p...');
console.log('Balance:', balance, 'satoshis');
typescript
Single address balance with ordinal filtering
// Get total balance including ordinals
const totalBalance = await provider.getBalance(wallet.p2tr, false);

// Get spendable balance excluding ordinals
const spendableBalance = await provider.getBalance(wallet.p2tr, true);

console.log('Total balance:', totalBalance);
console.log('Spendable balance:', spendableBalance);
console.log('Ordinal value:', totalBalance - spendableBalance);

Multiple Address Balances

The getBalances() method retrieves satoshi balances for multiple addresses in a single request. It accepts an array of addresses and an optional filterOrdinals flag, returning a Record<string, bigint> that maps each address to its balance.

Use this method instead of calling getBalance() in a loop to reduce network round-trips.

typescript
Multiple addresses balances with options
// Get balances for multiple addresses efficiently
const addresses = [
    'bcrt1p...address1...',
    'bcrt1p...address2...',
    'bcrt1p...address3...',
];

const balances = await provider.getBalances(addresses, true);

// balances is Record<string, bigint>
for (const [address, balance] of Object.entries(balances)) {
    console.log(`${address}: ${balance} sats`);
}
typescript
Multiple addresses balances with total calculation
async function getTotalBalance(
    provider: JSONRpcProvider,
    addresses: string[]
): Promise<bigint> {
    const balances = await provider.getBalances(addresses, true);

    let total = 0n;
    for (const balance of Object.values(balances)) {
        total += balance;
    }

    return total;
}

// Usage
const total = await getTotalBalance(provider, [
    wallet.p2tr,
    wallet.p2wpkh,
]);
console.log('Total across addresses:', total, 'sats');
        

Formatting Balances

typescript
Convert balance to BTC
import { BitcoinUtils } from 'opnet';

const balanceSats = await provider.getBalance(address);
const balanceBTC = BitcoinUtils.formatUnits(balanceSats, 8);

console.log(`Balance: ${balanceBTC} BTC`);
typescript
Convert balance to a human-readable format
function formatBalance(satoshis: bigint): string {
    if (satoshis >= 100_000_000n) {
        // Format as BTC for large amounts
        const btc = Number(satoshis) / 100_000_000;
        return `${btc.toFixed(8)} BTC`;
    } else if (satoshis >= 1_000_000n) {
        // Format as mBTC
        const mbtc = Number(satoshis) / 100_000;
        return `${mbtc.toFixed(5)} mBTC`;
    } else {
        // Format as sats
        return `${satoshis.toLocaleString()} sats`;
    }
}

const balance = await provider.getBalance(address);
console.log('Balance:', formatBalance(balance));

Wallet Balance Tracking

Implementing a balance cache avoids redundant network requests when the same addresses are queried repeatedly. The BalanceService below stores results locally and invalidates them on new block confirmations.

typescript
Complete balance service example
class BalanceService {
    private provider: JSONRpcProvider;
    private cache: Map<string, { balance: bigint; timestamp: number }> = new Map();
    private cacheTimeout = 30000; // 30 seconds

    constructor(provider: JSONRpcProvider) {
        this.provider = provider;
    }

    async getBalance(
        address: string,
        useCache: boolean = true
    ): Promise<bigint> {
        if (useCache) {
            const cached = this.cache.get(address);
            if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
                return cached.balance;
            }
        }

        const balance = await this.provider.getBalance(address, true);
        this.cache.set(address, { balance, timestamp: Date.now() });

        return balance;
    }

    async getMultipleBalances(
        addresses: string[],
        useCache: boolean = true
    ): Promise<Record<string, bigint>> {
        if (!useCache) {
            return this.provider.getBalances(addresses, true);
        }

        const result: Record<string, bigint> = {};
        const addressesToFetch: string[] = [];

        // Check cache first
        for (const address of addresses) {
            const cached = this.cache.get(address);
            if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
                result[address] = cached.balance;
            } else {
                addressesToFetch.push(address);
            }
        }

        // Fetch uncached addresses
        if (addressesToFetch.length > 0) {
            const freshBalances = await this.provider.getBalances(
                addressesToFetch,
                true
            );

            for (const [address, balance] of Object.entries(freshBalances)) {
                result[address] = balance;
                this.cache.set(address, { balance, timestamp: Date.now() });
            }
        }

        return result;
    }

    clearCache(address?: string): void {
        if (address) {
            this.cache.delete(address);
        } else {
            this.cache.clear();
        }
    }
}

// Usage
const balanceService = new BalanceService(provider);

const balance = await balanceService.getBalance(wallet.p2tr);
console.log('Balance:', balance);

// Clear cache after transaction
balanceService.clearCache(wallet.p2tr);

Balance Monitoring

Polling

Polling periodically for balance changes allows applications to detect incoming payments or withdrawals without a persistent WebSocket connection. The approach below queries the provider at a fixed interval and triggers a callback when the balance differs from the previously known value.

typescript
Polling for balance changes
async function monitorBalance(
    provider: JSONRpcProvider,
    address: string,
    callback: (balance: bigint) => void,
    intervalMs: number = 10000
): Promise<() => void> {
    let previousBalance = await provider.getBalance(address, true);
    callback(previousBalance);

    const intervalId = setInterval(async () => {
        try {
            const currentBalance = await provider.getBalance(address, true);

            if (currentBalance !== previousBalance) {
                callback(currentBalance);
                previousBalance = currentBalance;
            }
        } catch (error) {
            console.error('Error checking balance:', error);
        }
    }, intervalMs);

    // Return cleanup function
    return () => clearInterval(intervalId);
}

// Usage
const stopMonitoring = await monitorBalance(
    provider,
    wallet.p2tr,
    (balance) => {
        console.log('Balance updated:', balance, 'sats');
    },
    5000  // Check every 5 seconds
);

// Later: stop monitoring
// stopMonitoring();

Real-Time

Warning
Note that WebSocketRpcProvider is currently experimental.

For real-time balance monitoring without polling overhead, subscribe to new block notifications via the WebSocketRpcProvider and re-query the balance on each confirmed block. This approach reacts immediately to on-chain changes while avoiding unnecessary requests between blocks.

typescript
Using webSocket for real-time updates
import { WebSocketRpcProvider } from 'opnet';

const wsProvider = new WebSocketRpcProvider({ url: 'wss://regtest.opnet.org/ws', network });

// Subscribe to block notifications
wsProvider.subscribeToBlocks((block) => {
    // Check balance after each new block
    provider.getBalance(wallet.p2tr).then((balance) => {
        console.log(`Block ${block.height}: Balance = ${balance} sats`);
    });
});