BigBlocks Docs
Components/Wallet

SimpleTokenBalance

A lightweight, minimal token balance display component that shows cryptocurrency balances with clean formatting

A lightweight, minimal token balance display component that provides clean and customizable cryptocurrency balance formatting, perfect for compact UIs, dashboards, sidebar displays, and anywhere you need to show token balances without the overhead of a full wallet interface.

View Component Preview →

Installation

npx bigblocks add simple-token-balance

Import

import { SimpleTokenBalance } from 'bigblocks';

Props

PropTypeRequiredDefaultDescription
symbolstringYes-Token symbol (BSV, PEPE, USD, etc.)
balancenumberYes-Balance amount to display
decimalsnumberNo8Number of decimal places to show
showSymbolbooleanNotrueWhether to show the token symbol
prefixstringNo-Prefix character (e.g., "$", "€")
suffixstringNo-Suffix text to append
loadingbooleanNofalseShow loading state
errorstringNo-Error message to display
onClick() => voidNo-Click handler for interactive balances
compactNotationbooleanNofalseUse compact notation (1.23K, 1.23M)
scientificNotationbooleanNofalseUse scientific notation for small numbers
thousandsSeparatorstringNo','Thousands separator character
decimalSeparatorstringNo'.'Decimal separator character
classNamestringNo-Additional CSS classes
styleReact.CSSPropertiesNo-Inline styles

Basic Usage

import { SimpleTokenBalance } from 'bigblocks';

export default function WalletSidebar() {
  return (
    <div className="wallet-sidebar">
      <h3>Balances</h3>
      
      {/* Basic BSV balance */}
      <SimpleTokenBalance 
        symbol="BSV"
        balance={0.12345678}
        showSymbol={true}
      />
      
      {/* Token balance with custom formatting */}
      <SimpleTokenBalance 
        symbol="PEPE"
        balance={1000000}
        decimals={0}
        showSymbol={true}
        className="token-balance"
      />
      
      {/* USD value display */}
      <SimpleTokenBalance 
        symbol="USD"
        balance={45.67}
        decimals={2}
        prefix="$"
        showSymbol={false}
      />
    </div>
  );
}

Advanced Usage

Complete Balance Display System

import { SimpleTokenBalance } from 'bigblocks';
import { useState, useEffect } from 'react';

interface TokenData {
  symbol: string;
  balance: number;
  usdValue?: number;
  change24h?: number;
}

export default function AdvancedTokenDisplay() {
  const [tokens, setTokens] = useState<TokenData[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    fetchTokenBalances();
  }, []);

  const fetchTokenBalances = async () => {
    try {
      const response = await fetch('/api/wallet/balances');
      const data = await response.json();
      setTokens(data.tokens);
      setLoading(false);
    } catch (err) {
      setError('Failed to load balances');
      setLoading(false);
    }
  };

  const handleBalanceClick = (symbol: string) => {
    console.log(`Clicked on ${symbol} balance`);
    // Navigate to token details or show transaction history
    window.location.href = `/wallet/token/${symbol}`;
  };

  if (loading) {
    return (
      <div className="token-display">
        <SimpleTokenBalance symbol="BSV" balance={0} loading={true} />
        <SimpleTokenBalance symbol="USD" balance={0} loading={true} />
      </div>
    );
  }

  if (error) {
    return (
      <SimpleTokenBalance 
        symbol="BSV" 
        balance={0} 
        error={error}
        onClick={() => fetchTokenBalances()} // Retry on click
      />
    );
  }

  return (
    <div className="advanced-token-display">
      <h2>Your Portfolio</h2>
      
      <div className="balance-grid">
        {tokens.map((token) => (
          <div key={token.symbol} className="balance-item">
            <SimpleTokenBalance 
              symbol={token.symbol}
              balance={token.balance}
              decimals={token.symbol === 'BSV' ? 8 : 0}
              onClick={() => handleBalanceClick(token.symbol)}
              className="hover:opacity-80 cursor-pointer"
            />
            
            {token.usdValue && (
              <SimpleTokenBalance 
                symbol="USD"
                balance={token.usdValue}
                decimals={2}
                prefix="$"
                showSymbol={false}
                className="text-sm text-gray-500"
              />
            )}
            
            {token.change24h && (
              <span className={`text-xs ${token.change24h >= 0 ? 'text-green-500' : 'text-red-500'}`}>
                {token.change24h >= 0 ? '+' : ''}{token.change24h.toFixed(2)}%
              </span>
            )}
          </div>
        ))}
      </div>
      
      <div className="total-value mt-4 pt-4 border-t">
        <span className="text-lg font-semibold">Total Value:</span>
        <SimpleTokenBalance 
          symbol="USD"
          balance={tokens.reduce((sum, t) => sum + (t.usdValue || 0), 0)}
          decimals={2}
          prefix="$"
          showSymbol={false}
          className="text-xl font-bold"
        />
      </div>
    </div>
  );
}

Large Number Formatting

import { SimpleTokenBalance } from 'bigblocks';

export default function LargeNumberExamples() {
  return (
    <div className="large-number-examples">
      <h3>Large Number Formatting</h3>
      
      {/* Compact notation for millions/billions */}
      <div className="example">
        <h4>Total Supply</h4>
        <SimpleTokenBalance 
          symbol="PEPE"
          balance={420690000000000}
          decimals={0}
          compactNotation={true} // Shows as "420.69T PEPE"
          className="text-2xl font-bold"
        />
      </div>
      
      {/* Scientific notation for very small amounts */}
      <div className="example">
        <h4>Dust Amount</h4>
        <SimpleTokenBalance 
          symbol="BSV"
          balance={0.00000546}
          scientificNotation={true} // Shows as "5.46e-6 BSV"
          className="font-mono text-sm"
        />
      </div>
      
      {/* Custom formatting for different regions */}
      <div className="example">
        <h4>European Format</h4>
        <SimpleTokenBalance 
          symbol="EUR"
          balance={1234567.89}
          decimals={2}
          thousandsSeparator="."
          decimalSeparator=","
          prefix="€"
          showSymbol={false} // Shows as "€1.234.567,89"
        />
      </div>
    </div>
  );
}

Common Patterns

Wallet Overview Widget

import { SimpleTokenBalance } from 'bigblocks';
import { useWallet } from 'bigblocks/hooks';

export default function WalletWidget() {
  const { balances, isLoading, error, refetch } = useWallet();

  return (
    <div className="wallet-widget">
      <div className="widget-header">
        <h3>Wallet</h3>
        <button onClick={refetch} className="refresh-btn">

        </button>
      </div>
      
      <div className="balance-list">
        {/* Main BSV balance */}
        <div className="balance-row">
          <SimpleTokenBalance 
            symbol="BSV"
            balance={balances?.bsv || 0}
            loading={isLoading}
            error={error}
            onClick={() => window.location.href = '/wallet/bsv'}
            className="font-semibold"
          />
        </div>
        
        {/* USD equivalent */}
        <div className="balance-row">
          <span className="text-gray-500">≈</span>
          <SimpleTokenBalance 
            symbol="USD"
            balance={balances?.usdValue || 0}
            decimals={2}
            prefix="$"
            showSymbol={false}
            loading={isLoading}
            className="text-gray-600"
          />
        </div>
        
        {/* Additional tokens */}
        {balances?.tokens?.map((token) => (
          <div key={token.symbol} className="balance-row">
            <SimpleTokenBalance 
              symbol={token.symbol}
              balance={token.balance}
              decimals={token.decimals}
              loading={isLoading}
              onClick={() => window.location.href = `/wallet/token/${token.symbol}`}
            />
          </div>
        ))}
      </div>
      
      {error && (
        <button 
          onClick={refetch}
          className="retry-button text-sm text-blue-500 hover:underline"
        >
          Retry loading balances
        </button>
      )}
    </div>
  );
}

Token Portfolio Display

import { SimpleTokenBalance } from 'bigblocks';
import { useState } from 'react';

interface Token {
  symbol: string;
  name: string;
  balance: number;
  decimals: number;
  usdValue: number;
  icon?: string;
}

interface TokenPortfolioProps {
  tokens: Token[];
}

export default function TokenPortfolio({ tokens }: TokenPortfolioProps) {
  const [sortBy, setSortBy] = useState<'value' | 'balance' | 'symbol'>('value');
  const [filterZero, setFilterZero] = useState(true);

  const processedTokens = tokens
    .filter(token => !filterZero || token.balance > 0)
    .sort((a, b) => {
      switch (sortBy) {
        case 'value': return b.usdValue - a.usdValue;
        case 'balance': return b.balance - a.balance;
        case 'symbol': return a.symbol.localeCompare(b.symbol);
        default: return 0;
      }
    });

  const totalValue = processedTokens.reduce((sum, token) => sum + token.usdValue, 0);

  return (
    <div className="token-portfolio">
      <div className="portfolio-header">
        <h2>Token Portfolio</h2>
        
        <div className="portfolio-controls">
          <select 
            value={sortBy} 
            onChange={(e) => setSortBy(e.target.value as any)}
            className="sort-select"
          >
            <option value="value">Sort by Value</option>
            <option value="balance">Sort by Balance</option>
            <option value="symbol">Sort by Symbol</option>
          </select>
          
          <label className="filter-checkbox">
            <input 
              type="checkbox"
              checked={filterZero}
              onChange={(e) => setFilterZero(e.target.checked)}
            />
            Hide zero balances
          </label>
        </div>
      </div>
      
      <div className="token-list">
        {processedTokens.map((token) => (
          <div key={token.symbol} className="token-item">
            <div className="token-info">
              {token.icon && <img src={token.icon} alt={token.symbol} className="token-icon" />}
              <div>
                <h4>{token.name}</h4>
                <span className="text-gray-500">{token.symbol}</span>
              </div>
            </div>
            
            <div className="token-balances">
              <SimpleTokenBalance 
                symbol={token.symbol}
                balance={token.balance}
                decimals={token.decimals}
                showSymbol={false}
                className="font-mono"
              />
              
              <SimpleTokenBalance 
                symbol="USD"
                balance={token.usdValue}
                decimals={2}
                prefix="$"
                showSymbol={false}
                className="text-gray-600"
              />
            </div>
          </div>
        ))}
      </div>
      
      <div className="portfolio-footer">
        <span className="text-lg font-semibold">Total Portfolio Value</span>
        <SimpleTokenBalance 
          symbol="USD"
          balance={totalValue}
          decimals={2}
          prefix="$"
          showSymbol={false}
          className="text-2xl font-bold text-green-600"
        />
      </div>
    </div>
  );
}

Gaming Currency Display

import { SimpleTokenBalance } from 'bigblocks';

interface GameCurrency {
  type: 'gold' | 'gems' | 'energy' | 'tickets';
  amount: number;
  icon: string;
}

interface GameWalletProps {
  currencies: GameCurrency[];
  playerLevel: number;
}

export default function GameWallet({ currencies, playerLevel }: GameWalletProps) {
  const getCurrencyConfig = (type: string) => {
    switch (type) {
      case 'gold':
        return { symbol: 'GOLD', icon: '🪙', decimals: 0, color: 'text-yellow-500' };
      case 'gems':
        return { symbol: 'GEMS', icon: '💎', decimals: 0, color: 'text-purple-500' };
      case 'energy':
        return { symbol: 'ENERGY', icon: '⚡', decimals: 0, color: 'text-blue-500' };
      case 'tickets':
        return { symbol: 'TIX', icon: '🎟️', decimals: 0, color: 'text-green-500' };
      default:
        return { symbol: type.toUpperCase(), icon: '🔷', decimals: 0, color: 'text-gray-500' };
    }
  };

  return (
    <div className="game-wallet">
      <div className="player-info">
        <span className="text-sm text-gray-600">Level {playerLevel}</span>
      </div>
      
      <div className="currency-grid">
        {currencies.map((currency) => {
          const config = getCurrencyConfig(currency.type);
          
          return (
            <div key={currency.type} className="currency-item">
              <span className="currency-icon text-2xl">{config.icon}</span>
              
              <SimpleTokenBalance 
                symbol={config.symbol}
                balance={currency.amount}
                decimals={config.decimals}
                showSymbol={false}
                compactNotation={currency.amount > 9999}
                className={`font-bold ${config.color}`}
                onClick={() => {
                  console.log(`Clicked ${currency.type}: ${currency.amount}`);
                  // Show shop or exchange interface
                }}
              />
            </div>
          );
        })}
      </div>
      
      <div className="quick-actions">
        <button className="buy-more-btn">
          Buy More 💰
        </button>
      </div>
    </div>
  );
}

Real-time Price Ticker

import { SimpleTokenBalance } from 'bigblocks';
import { useState, useEffect } from 'react';

interface PriceData {
  symbol: string;
  price: number;
  change24h: number;
  volume24h: number;
}

export default function PriceTicker() {
  const [prices, setPrices] = useState<PriceData[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Initial fetch
    fetchPrices();
    
    // Update every 30 seconds
    const interval = setInterval(fetchPrices, 30000);
    
    return () => clearInterval(interval);
  }, []);

  const fetchPrices = async () => {
    try {
      const response = await fetch('/api/prices/ticker');
      const data = await response.json();
      setPrices(data.prices);
      setLoading(false);
    } catch (error) {
      console.error('Failed to fetch prices:', error);
      setLoading(false);
    }
  };

  return (
    <div className="price-ticker">
      <div className="ticker-header">
        <h3>Market Prices</h3>
        <span className="update-indicator">● Live</span>
      </div>
      
      <div className="ticker-scroll">
        {prices.map((price) => (
          <div key={price.symbol} className="ticker-item">
            <div className="ticker-symbol">{price.symbol}</div>
            
            <SimpleTokenBalance 
              symbol="USD"
              balance={price.price}
              decimals={price.price < 1 ? 6 : 2}
              prefix="$"
              showSymbol={false}
              loading={loading}
              className="font-mono"
            />
            
            <div className={`ticker-change ${price.change24h >= 0 ? 'text-green-500' : 'text-red-500'}`}>
              {price.change24h >= 0 ? '▲' : '▼'} {Math.abs(price.change24h).toFixed(2)}%
            </div>
            
            <div className="ticker-volume">
              <span className="text-xs text-gray-500">Vol:</span>
              <SimpleTokenBalance 
                symbol="USD"
                balance={price.volume24h}
                decimals={0}
                prefix="$"
                showSymbol={false}
                compactNotation={true}
                className="text-xs"
              />
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

Formatting Options

Compact Notation for Large Numbers

import { SimpleTokenBalance } from 'bigblocks';

export default function CompactNotationExamples() {
  return (
    <div className="compact-examples">
      {/* Thousands */}
      <SimpleTokenBalance 
        symbol="SATS"
        balance={12345}
        decimals={0}
        compactNotation={true} // Shows as "12.3K SATS"
      />
      
      {/* Millions */}
      <SimpleTokenBalance 
        symbol="PEPE"
        balance={123456789}
        decimals={0}
        compactNotation={true} // Shows as "123.5M PEPE"
      />
      
      {/* Billions */}
      <SimpleTokenBalance 
        symbol="SHIB"
        balance={1234567890123}
        decimals={0}
        compactNotation={true} // Shows as "1.23T SHIB"
      />
    </div>
  );
}

Scientific Notation for Small Numbers

import { SimpleTokenBalance } from 'bigblocks';

export default function ScientificNotationExamples() {
  return (
    <div className="scientific-examples">
      {/* Very small BSV amounts */}
      <SimpleTokenBalance 
        symbol="BSV"
        balance={0.00000123}
        scientificNotation={true} // Shows as "1.23e-6 BSV"
      />
      
      {/* Dust amounts */}
      <SimpleTokenBalance 
        symbol="BTC"
        balance={0.00000546}
        scientificNotation={true} // Shows as "5.46e-6 BTC"
      />
    </div>
  );
}

Regional Number Formatting

import { SimpleTokenBalance } from 'bigblocks';

export default function RegionalFormatting() {
  return (
    <div className="regional-formatting">
      {/* US Format (default) */}
      <SimpleTokenBalance 
        symbol="USD"
        balance={1234567.89}
        decimals={2}
        prefix="$"
        showSymbol={false} // Shows as "$1,234,567.89"
      />
      
      {/* European Format */}
      <SimpleTokenBalance 
        symbol="EUR"
        balance={1234567.89}
        decimals={2}
        thousandsSeparator="."
        decimalSeparator=","
        prefix="€"
        showSymbol={false} // Shows as "€1.234.567,89"
      />
      
      {/* Indian Format */}
      <SimpleTokenBalance 
        symbol="INR"
        balance={12345678.90}
        decimals={2}
        thousandsSeparator=","
        decimalSeparator="."
        prefix="₹"
        showSymbol={false}
        // Custom formatting would show as "₹1,23,45,678.90"
      />
    </div>
  );
}

Authentication Requirements

SimpleTokenBalance is a display-only component and does not require authentication context. However, it's commonly used within authenticated components:

import { 
  BitcoinAuthProvider, 
  BitcoinQueryProvider, 
  SimpleTokenBalance 
} from 'bigblocks';

function App() {
  // SimpleTokenBalance can be used without auth context
  return (
    <div>
      {/* Outside auth context - for public display */}
      <SimpleTokenBalance 
        symbol="BSV"
        balance={0.12345}
      />
      
      {/* Inside auth context - for authenticated balance display */}
      <BitcoinQueryProvider>
        <BitcoinAuthProvider config={{ apiUrl: '/api' }}>
          <AuthenticatedBalances />
        </BitcoinAuthProvider>
      </BitcoinQueryProvider>
    </div>
  );
}

Features

  • Minimal Design: Clean, unobtrusive balance display that fits anywhere
  • Smart Formatting: Automatic number formatting with locale awareness
  • Loading States: Built-in loading spinner and skeleton animations
  • Error Handling: Graceful error display with optional retry
  • Click Interaction: Optional click handler for balance interaction
  • Responsive Design: Adapts to container width automatically
  • Theme Integration: Inherits Bitcoin theme colors and typography
  • Accessibility: Screen reader friendly with proper ARIA labels
  • Memory Efficient: Minimal DOM footprint with memoized rendering

Best Practices

  1. Consistent Decimals: Use consistent decimal places for the same token type across your app
  2. Loading States: Always provide loading states for async balance data
  3. Error Handling: Gracefully handle and display balance loading errors
  4. Responsive Design: Ensure balance displays work on mobile devices
  5. Currency Symbols: Use standard currency symbols and token tickers
  6. Click Feedback: Provide visual feedback when balances are clickable

Styling

The component uses minimal styling and is designed to inherit from parent containers:

/* Default styles applied to SimpleTokenBalance */
.simple-token-balance {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-variant-numeric: tabular-nums; /* Ensures numbers align properly */
}

/* Loading state animation */
.balance-loading {
  opacity: 0.6;
  animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

/* Error state styling */
.balance-error {
  color: var(--color-error, #ef4444);
  font-style: italic;
}

/* Clickable balance hover effect */
.balance-clickable {
  cursor: pointer;
  transition: opacity 0.2s ease-in-out;
}

.balance-clickable:hover {
  opacity: 0.8;
}

/* Skeleton loading animation */
@keyframes pulse {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.5;
  }
}

Performance Optimization

Memoization for Large Lists

import { SimpleTokenBalance } from 'bigblocks';
import { memo } from 'react';

// Memoized balance item for performance
const MemoizedBalanceItem = memo(({ token }: { token: TokenData }) => (
  <SimpleTokenBalance 
    symbol={token.symbol}
    balance={token.balance}
    decimals={token.decimals}
  />
));

export default function LargeTokenList({ tokens }: { tokens: TokenData[] }) {
  return (
    <div className="large-token-list">
      {tokens.map((token) => (
        <MemoizedBalanceItem key={token.symbol} token={token} />
      ))}
    </div>
  );
}

Troubleshooting

Common Issues

Balance not updating

  • Ensure you're passing updated balance values as props
  • Check that the component isn't memoized with stale data
  • Verify data fetching is working correctly

Formatting issues

  • Check decimal places match the token's standard (BSV uses 8, USD uses 2)
  • Verify separator characters are appropriate for the locale
  • Ensure numbers are valid JavaScript numbers, not strings

Click handler not working

  • Verify onClick prop is passed correctly
  • Check for event propagation issues in parent components
  • Ensure the component isn't disabled or covered by other elements

Loading state stuck

  • Check that loading prop is properly controlled
  • Verify async operations complete and update state
  • Look for errors in data fetching logic

API Reference

SimpleTokenBalance Component

interface SimpleTokenBalanceProps {
  symbol: string;
  balance: number;
  decimals?: number;
  showSymbol?: boolean;
  prefix?: string;
  suffix?: string;
  loading?: boolean;
  error?: string;
  onClick?: () => void;
  compactNotation?: boolean;
  scientificNotation?: boolean;
  thousandsSeparator?: string;
  decimalSeparator?: string;
  className?: string;
  style?: React.CSSProperties;
}

Usage with Custom Hooks

import { SimpleTokenBalance } from 'bigblocks';
import { useTokenBalance } from './hooks/useTokenBalance';

function TokenBalanceWithHook({ tokenSymbol }: { tokenSymbol: string }) {
  const { balance, loading, error, refetch } = useTokenBalance(tokenSymbol);

  return (
    <SimpleTokenBalance 
      symbol={tokenSymbol}
      balance={balance || 0}
      loading={loading}
      error={error}
      onClick={error ? refetch : undefined}
    />
  );
}