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
static formatUnits(
value: BigNumberish, // Value to format (bigint, string, or number)
decimals: number = 8 // Number of decimal places (default: 8)
): stringConvert 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
// 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
static expandToDecimals(
n: number | string, // Human-readable amount
decimals: number | string // Number of decimal places
): bigintConvert 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); // 100000nToken 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}`); // 50050000000nRounding 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.
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.
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
function isP2MRAddress(addr: string, network: Network): booleanScript 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 |
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().
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.
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.
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
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