Bitcoin Utils

Overview

The BitcoinUtils class provides helper functions for common Bitcoin value conversions. These utilities simplify formatting satoshi values for display and expanding decimal values back to satoshis for calculations. The class handles the precision requirements of Bitcoin arithmetic, ensuring accurate conversions between human-readable and computational formats.

Formatting Units

Using formatUnits()

The formatUnits() method converts raw integer values to human-readable decimal strings. This is essential for displaying satoshi amounts as Bitcoin or token balances with proper decimal formatting. The method accepts a value as bigint, string, or number, along with the number of decimal places to apply.

By default, the method uses 8 decimal places to match Bitcoin's precision, but this can be adjusted for tokens with different decimal configurations. The returned string includes only significant digits, removing unnecessary trailing zeros for cleaner display output.

Method Signature

typescript
formatUnits() signature
static formatUnits(
    value: BigNumberish,    // Value to format (bigint, string, or number)
    decimals: number = 8    // Number of decimal places (default: 8)
): string

Convert to Display Format

typescript
Convert to display format
import { BitcoinUtils } from 'opnet';

// Convert satoshis to Bitcoin (8 decimals)
const btcAmount = BitcoinUtils.formatUnits(100000000n, 8);
console.log(btcAmount); // "1"

// Convert satoshis to Bitcoin with decimal
const btcAmount2 = BitcoinUtils.formatUnits(150000000n, 8);
console.log(btcAmount2); // "1.5"

// Convert from smaller amounts
const btcAmount3 = BitcoinUtils.formatUnits(1234n, 8);
console.log(btcAmount3); // "0.00001234"

Token Formatting

typescript
Token formatting
// Format OP20 token balance (typically 8 decimals)
const tokenBalance = 50000000000n; // Raw balance
const formatted = BitcoinUtils.formatUnits(tokenBalance, 8);
console.log(`Balance: ${formatted} tokens`); // "Balance: 500 tokens"

// Format with different decimals
const usdtBalance = 1000000n; // 6 decimals
const usdtFormatted = BitcoinUtils.formatUnits(usdtBalance, 6);
console.log(`USDT: ${usdtFormatted}`); // "USDT: 1"

Expanding Decimal Values

Using expandToDecimals()

The expandToDecimals() method converts human-readable decimal values to raw integer format suitable for contract interactions. This is the inverse of formatUnits(), transforming display values like "1.5" back to their full precision representation such as 150000000n for 8 decimal places.

The method accepts the amount as a number or string and the decimal places as either number or string. It returns a bigint representing the expanded value, ready for use in transaction parameters and contract method calls.

Method Signature

typescript
expandToDecimals() signature
static expandToDecimals(
    n: number | string,           // Human-readable amount
    decimals: number | string     // Number of decimal places
): bigint

Convert to BigInt

typescript
Convert to BigInt
import { BitcoinUtils } from 'opnet';

// Convert 1 Bitcoin to satoshis
const satoshis = BitcoinUtils.expandToDecimals(1, 8);
console.log(satoshis); // 100000000n

// Convert 1.5 Bitcoin to satoshis
const satoshis2 = BitcoinUtils.expandToDecimals(1.5, 8);
console.log(satoshis2); // 150000000n

// Convert 0.001 Bitcoin to satoshis
const satoshis3 = BitcoinUtils.expandToDecimals(0.001, 8);
console.log(satoshis3); // 100000n

Token Amounts

typescript
Token Amounts
// Convert token amount for transaction
const tokenAmount = BitcoinUtils.expandToDecimals(100, 8);
console.log(`Transfer amount: ${tokenAmount}`); // 10000000000n

// Convert with string input
const amount = BitcoinUtils.expandToDecimals('500.5', 8);
console.log(`Amount: ${amount}`); // 50050000000n

Rounding and Precision

Handle Decimal Precision

Display requirements may differ from the token's native decimal precision. The following example demonstrates formatting a value with a specific number of display decimals, useful for condensed UI elements where full precision is unnecessary.

typescript
Format with precision
function formatWithPrecision(
    amount: bigint,
    decimals: number,
    displayDecimals: number = 4
): string {
    const formatted = BitcoinUtils.formatUnits(amount, decimals);
    const num = parseFloat(formatted);

    return num.toFixed(displayDecimals);
}

// Usage
const display = formatWithPrecision(123456789n, 8, 4);
console.log(display); // "1.2346"

Round to Satoshis

Bitcoin values beyond 8 decimal places cannot be represented on-chain. The following example demonstrates converting a decimal string to satoshis, with excess precision automatically truncated to the maximum supported decimal places.

typescript
Round to Satoshis
function roundToSatoshis(btcAmount: string): bigint {
    // Expand then re-format to ensure proper rounding
    const satoshis = BitcoinUtils.expandToDecimals(btcAmount, 8);
    return satoshis;
}

// Usage
const sats = roundToSatoshis('0.123456789');
console.log(sats); // 12345678n (9th decimal truncated)

Detecting P2MR Addresses

Using isP2MRAddress()

The isP2MRAddress() function identifies Pay-to-Merkle-Root (BIP 360) addresses on a given network. P2MR addresses use witness version 2 with a 32-byte Merkle root, enabling advanced scripting capabilities for quantum-resistant transactions.

The method accepts an address string and the target network configuration, returning true if the address conforms to the P2MR format. This utility is useful for validating address types before processing or for routing logic that handles different address formats.

Method Signature

typescript
isP2MRAddress() signature
function isP2MRAddress(addr: string, network: Network): boolean

Script Constants

Two magic-byte constants are re-exported from @btc-vision/transaction for low-level script construction. These constants identify specific output script types when building or parsing transactions:

Constant Description
P2MR_MS Magic byte identifying P2MR (BIP 360) script outputs
P2TR_MS Magic byte identifying P2TR (Taproot) script outputs
typescript
Magic byte constants
import { P2MR_MS, P2TR_MS } from 'opnet';

Practical Examples

Display Token Balance

Token balances retrieved from contracts are returned as raw integer values. The following example demonstrates fetching a token balance and its decimal configuration, then formatting the result for user display using formatUnits().

typescript
Display token balance
async function displayTokenBalance(
    contract: IOP20Contract,
    userAddress: Address
): Promise<void> {
    // Get raw balance
    const result = await contract.balanceOf(userAddress);
    const rawBalance = result.properties.balance;

    // Get decimals
    const decimalsResult = await contract.decimals();
    const decimals = Number(decimalsResult.properties.decimals);

    // Format for display
    const formatted = BitcoinUtils.formatUnits(rawBalance, decimals);
    console.log(`Balance: ${formatted}`);
}

Parse User Input

User input from forms and text fields requires validation before conversion. The following example demonstrates a utility function that validates input strings and converts them to raw integer values using expandToDecimals(), ensuring safe handling of user-provided amounts.

typescript
Parse user input
function parseUserAmount(
    input: string,
    decimals: number
): bigint {
    // Handle empty or invalid input
    if (!input || isNaN(parseFloat(input))) {
        throw new Error('Invalid amount');
    }

    // Convert to BigInt
    return BitcoinUtils.expandToDecimals(input, decimals);
}

// Usage
const userInput = '100.5';
const amount = parseUserAmount(userInput, 8);
console.log(`Sending: ${amount} satoshi units`);

Format with Symbol

The base formatting methods can be extended to include additional display features such as thousands separators and currency symbols. The following example demonstrates a utility function that combines formatUnits() with locale-aware formatting for enhanced readability in user interfaces.

typescript
Custom formatting
function formatTokenAmount(
    amount: bigint,
    decimals: number,
    symbol: string
): string {
    const formatted = BitcoinUtils.formatUnits(amount, decimals);

    // Add thousands separator
    const parts = formatted.split('.');
    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');

    return `${parts.join('.')} ${symbol}`;
}

// Usage
const formatted = formatTokenAmount(1234567890000n, 8, 'BTC');
console.log(formatted); // "12,345.6789 BTC"

Complete Utility Service

typescript
Utility service
class TokenFormatter {
    private decimals: number;
    private symbol: string;

    constructor(decimals: number, symbol: string) {
        this.decimals = decimals;
        this.symbol = symbol;
    }

    formatBalance(raw: bigint): string {
        return BitcoinUtils.formatUnits(raw, this.decimals);
    }

    formatWithSymbol(raw: bigint): string {
        const formatted = this.formatBalance(raw);
        return `${formatted} ${this.symbol}`;
    }

    parseAmount(display: string): bigint {
        return BitcoinUtils.expandToDecimals(display, this.decimals);
    }

    formatCompact(raw: bigint): string {
        const num = parseFloat(this.formatBalance(raw));

        if (num >= 1e9) {
            return `${(num / 1e9).toFixed(2)}B ${this.symbol}`;
        } else if (num >= 1e6) {
            return `${(num / 1e6).toFixed(2)}M ${this.symbol}`;
        } else if (num >= 1e3) {
            return `${(num / 1e3).toFixed(2)}K ${this.symbol}`;
        }

        return `${num.toFixed(4)} ${this.symbol}`;
    }
}

// Usage
const btcFormatter = new TokenFormatter(8, 'BTC');

// Format balance
console.log(btcFormatter.formatWithSymbol(100000000n)); // "1 BTC"
console.log(btcFormatter.formatCompact(1500000000000n)); // "15.00K BTC"

// Parse amount
const sats = btcFormatter.parseAmount('0.5');
console.log(sats); // 50000000n