BigBlocks Docs
Components/Wallet

TokenBalance

Display token balances with icons and formatted values for BSV20, BSV21, and other token standards.

Display token balances with icons and formatted values for BSV20, BSV21, and other token standards. Features real-time price data, portfolio tracking, and interactive token management.

View TokenBalance examples →

Installation

npm install bigblocks

Usage

import { TokenBalance } from 'bigblocks';

export default function WalletTokens() {
  const tokens = [
    {
      symbol: 'MNEE',
      name: 'MNEE Token',
      balance: { confirmed: '1000', unconfirmed: '0', total: '1000' },
      icon: '🪙',
      price: { bsv: 0.001, usd: 0.05 }
    },
    {
      symbol: 'SHUA',
      name: 'SHUA Token', 
      balance: { confirmed: '500', unconfirmed: '0', total: '500' },
      icon: '💎',
      price: { bsv: 0.002, usd: 0.10 }
    }
  ];

  return (
    <TokenBalance
      tokens={tokens}
      onViewDetails={(token) => {
        router.push(`/tokens/${token.symbol}`);
      }}
      onSendToken={(token) => {
        setShowSendModal(token);
      }}
    />
  );
}

Props

PropTypeDefaultDescription
tokensTokenBalanceType[][]Array of token balances
showValuesbooleantrueShow token values
onToggleValues() => void-Toggle value visibility
onViewDetails(token: TokenBalanceType) => void-View token details callback
onSendToken(token: TokenBalanceType) => void-Send token callback
currency'BSV' | 'USD''BSV'Display currency
loadingbooleanfalseShow loading state
classNamestring-Additional CSS classes

TokenBalanceType Interface

interface TokenBalanceType {
  symbol: string;           // Token symbol (e.g., "MNEE")
  name: string;             // Full token name
  contractId?: string;      // Token contract identifier
  balance: {
    confirmed: string;      // Confirmed balance
    unconfirmed: string;    // Pending balance
    total: string;          // Total balance
  };
  decimals?: number;        // Decimal places
  icon?: string;            // Icon URL or emoji
  price?: {
    bsv: number;            // Price in BSV
    usd: number;            // Price in USD
    change24h?: number;     // 24h change percentage
  };
  value?: {
    bsv: number;            // Total value in BSV
    usd: number;            // Total value in USD
  };
  type?: 'BSV20' | 'BSV21' | 'BSV721' | 'custom';
}

Features

  • Multi-Standard Support: BSV20, BSV21, BSV721, and custom tokens
  • Real-time Pricing: Live token prices in BSV and USD
  • Portfolio Value: Calculate total portfolio worth
  • Interactive Actions: Send, view details, and manage tokens
  • Privacy Controls: Toggle value visibility
  • Loading States: Skeleton loading for better UX
  • Responsive Design: Mobile-optimized layout

Examples

Basic Token Display

<TokenBalance
  tokens={[
    {
      symbol: 'MNEE',
      name: 'MNEE Token',
      balance: { confirmed: '1000000', unconfirmed: '0', total: '1000000' },
      icon: '🪙',
      decimals: 8
    }
  ]}
/>

With Price Data

<TokenBalance
  tokens={[
    {
      symbol: 'PEPE',
      name: 'PEPE Token',
      balance: { confirmed: '420000', unconfirmed: '0', total: '420000' },
      icon: '🐸',
      price: { bsv: 0.000001, usd: 0.00005, change24h: 15.3 },
      value: { bsv: 0.42, usd: 21.0 }
    }
  ]}
  currency="USD"
/>

Interactive Token Management

<TokenBalance
  tokens={userTokens}
  onViewDetails={(token) => {
    setSelectedToken(token);
    setShowDetailsModal(true);
  }}
  onSendToken={(token) => {
    setTokenToSend(token);
    setShowSendModal(true);
  }}
  onToggleValues={() => {
    setShowValues(!showValues);
  }}
  showValues={showValues}
/>

Loading State

<TokenBalance
  tokens={[]}
  loading={true}
/>

Simple Version

import { SimpleTokenBalance } from 'bigblocks';

<SimpleTokenBalance
  tokens={tokens}
  currency="BSV"
  className="compact-tokens"
/>

Portfolio Overview

function TokenPortfolio() {
  const [tokens, setTokens] = useState([]);
  const [totalValue, setTotalValue] = useState({ bsv: 0, usd: 0 });
  const [showValues, setShowValues] = useState(true);

  useEffect(() => {
    const total = tokens.reduce((acc, token) => ({
      bsv: acc.bsv + (token.value?.bsv || 0),
      usd: acc.usd + (token.value?.usd || 0)
    }), { bsv: 0, usd: 0 });
    
    setTotalValue(total);
  }, [tokens]);

  return (
    <div className="token-portfolio">
      <div className="portfolio-header">
        <h2>Token Portfolio</h2>
        <div className="total-value">
          <span className="value">
            {showValues ? `${totalValue.bsv.toFixed(8)} BSV` : '••••••••'}
          </span>
          <span className="usd-value">
            {showValues ? `$${totalValue.usd.toFixed(2)}` : '$••••'}
          </span>
        </div>
      </div>
      
      <TokenBalance
        tokens={tokens}
        showValues={showValues}
        onToggleValues={() => setShowValues(!showValues)}
        onViewDetails={(token) => {
          router.push(`/portfolio/token/${token.symbol}`);
        }}
        onSendToken={(token) => {
          router.push(`/send?token=${token.symbol}`);
        }}
      />
    </div>
  );
}

Token Watchlist

function TokenWatchlist() {
  const [watchedTokens, setWatchedTokens] = useState([]);
  const [priceAlerts, setPriceAlerts] = useState({});

  return (
    <div className="token-watchlist">
      <h3>Watched Tokens</h3>
      
      <TokenBalance
        tokens={watchedTokens}
        onViewDetails={(token) => {
          // Show token details with price history
          showTokenAnalytics(token);
        }}
        onSendToken={(token) => {
          // Quick send for watched tokens
          quickSendToken(token);
        }}
      />
      
      {Object.entries(priceAlerts).map(([symbol, alert]) => (
        <div key={symbol} className="price-alert">
          🚨 {symbol} price {alert.type}: {alert.price}
        </div>
      ))}
    </div>
  );
}

Multi-Wallet Token View

function MultiWalletTokens() {
  const [wallets, setWallets] = useState([]);
  const [selectedWallet, setSelectedWallet] = useState(0);

  return (
    <div className="multi-wallet-tokens">
      <div className="wallet-selector">
        {wallets.map((wallet, index) => (
          <button
            key={index}
            onClick={() => setSelectedWallet(index)}
            className={selectedWallet === index ? 'active' : ''}
          >
            {wallet.name}
          </button>
        ))}
      </div>
      
      <TokenBalance
        tokens={wallets[selectedWallet]?.tokens || []}
        onViewDetails={(token) => {
          // Show token details for selected wallet
          showWalletTokenDetails(selectedWallet, token);
        }}
        onSendToken={(token) => {
          // Send from selected wallet
          sendFromWallet(selectedWallet, token);
        }}
      />
    </div>
  );
}

Token Trading Interface

function TokenTradingInterface() {
  const [tokens, setTokens] = useState([]);
  const [marketData, setMarketData] = useState({});

  return (
    <div className="token-trading">
      <TokenBalance
        tokens={tokens.map(token => ({
          ...token,
          // Add market data
          price: marketData[token.symbol]?.price,
          value: {
            bsv: parseFloat(token.balance.total) * (marketData[token.symbol]?.price?.bsv || 0),
            usd: parseFloat(token.balance.total) * (marketData[token.symbol]?.price?.usd || 0)
          }
        }))}
        onViewDetails={(token) => {
          // Show trading view
          router.push(`/trade/${token.symbol}`);
        }}
        onSendToken={(token) => {
          // Quick sell option
          setTokenToSell(token);
          setShowSellModal(true);
        }}
      />
    </div>
  );
}

Required Context

The component requires the following providers:

import { 
  BitcoinAuthProvider, 
  BitcoinQueryProvider 
} from 'bigblocks';

function App() {
  return (
    <BitcoinAuthProvider>
      <BitcoinQueryProvider>
        <TokenBalance
          tokens={tokens}
          onViewDetails={handleViewDetails}
          onSendToken={handleSendToken}
        />
      </BitcoinQueryProvider>
    </BitcoinAuthProvider>
  );
}

API Integration

Required Backend Endpoints

The component expects these API endpoints:

1. Get Token Balances

GET /api/wallet/tokens?address=<address>

Response:
{
  tokens: Array<{
    symbol: string;
    name: string;
    contractId: string;
    balance: {
      confirmed: string;
      unconfirmed: string;
      total: string;
    };
    decimals: number;
    icon?: string;
    type: 'BSV20' | 'BSV21' | 'BSV721';
  }>;
  totalValue: {
    bsv: number;
    usd: number;
  };
}

2. Get Token Prices

GET /api/tokens/prices?symbols=<symbol1,symbol2>

Response:
{
  prices: {
    [symbol: string]: {
      bsv: number;
      usd: number;
      change24h: number;
      volume24h: number;
    };
  };
  lastUpdated: number;
}

3. Get Token Details

GET /api/tokens/{symbol}/details

Response:
{
  token: {
    symbol: string;
    name: string;
    description: string;
    contractId: string;
    type: string;
    decimals: number;
    totalSupply: string;
    website?: string;
    social?: object;
  };
  market: {
    price: TokenPrice;
    marketCap: number;
    volume24h: number;
    holders: number;
  };
}

Authentication Headers

For authenticated token operations:

headers: {
  'X-Auth-Token': '<BSM signature>',
  'Content-Type': 'application/json'
}

Token Standards

BSV20 Tokens

Fungible tokens following the BSV20 standard:

{
  type: 'BSV20',
  symbol: 'MNEE',
  name: 'MNEE Token',
  decimals: 8,
  totalSupply: '21000000'
}

BSV21 Tokens

Enhanced tokens with metadata:

{
  type: 'BSV21',
  symbol: 'SHUA',
  name: 'SHUA Token',
  decimals: 8,
  metadata: {
    description: 'Community token',
    image: 'https://example.com/icon.png',
    website: 'https://shua.com'
  }
}

BSV721 NFTs

Non-fungible tokens:

{
  type: 'BSV721',
  contractId: 'collection-123',
  tokenId: 'nft-456',
  name: 'Rare Art #456',
  metadata: {
    image: 'https://example.com/nft-456.png',
    attributes: [
      { trait_type: 'Color', value: 'Blue' },
      { trait_type: 'Rarity', value: 'Legendary' }
    ]
  }
}

Real-time Updates

WebSocket Integration

function LiveTokenBalance() {
  const [tokens, setTokens] = useState([]);

  useEffect(() => {
    const ws = new WebSocket('/api/wallet/tokens/stream');
    
    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      
      switch (data.type) {
        case 'balance_updated':
          setTokens(prev => 
            prev.map(token => 
              token.symbol === data.symbol 
                ? { ...token, balance: data.balance }
                : token
            )
          );
          break;
          
        case 'price_updated':
          setTokens(prev => 
            prev.map(token => 
              token.symbol === data.symbol 
                ? { ...token, price: data.price }
                : token
            )
          );
          break;
      }
    };

    return () => ws.close();
  }, []);

  return (
    <TokenBalance
      tokens={tokens}
      onViewDetails={handleViewDetails}
      onSendToken={handleSendToken}
    />
  );
}

Performance Considerations

  • Lazy Loading: Load token prices on demand
  • Caching: Cache token data and prices
  • Debounced Updates: Prevent excessive re-renders
  • Virtual Scrolling: Handle large token lists efficiently

Error Handling

Common Error Scenarios

function RobustTokenBalance() {
  const [tokens, setTokens] = useState([]);
  const [error, setError] = useState(null);
  const [retrying, setRetrying] = useState(false);

  const retryTokenLoad = async () => {
    setRetrying(true);
    try {
      const tokenData = await fetchTokenBalances();
      setTokens(tokenData);
      setError(null);
    } catch (err) {
      setError(err.message);
    } finally {
      setRetrying(false);
    }
  };

  if (error) {
    return (
      <div className="token-error">
        <p>Failed to load tokens: {error}</p>
        <button onClick={retryTokenLoad} disabled={retrying}>
          {retrying ? 'Retrying...' : 'Retry'}
        </button>
      </div>
    );
  }

  return (
    <TokenBalance
      tokens={tokens}
      onViewDetails={handleViewDetails}
      onSendToken={handleSendToken}
    />
  );
}