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.
Installation
npx bigblocks add simple-token-balanceImport
import { SimpleTokenBalance } from 'bigblocks';Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| symbol | string | Yes | - | Token symbol (BSV, PEPE, USD, etc.) |
| balance | number | Yes | - | Balance amount to display |
| decimals | number | No | 8 | Number of decimal places to show |
| showSymbol | boolean | No | true | Whether to show the token symbol |
| prefix | string | No | - | Prefix character (e.g., "$", "€") |
| suffix | string | No | - | Suffix text to append |
| loading | boolean | No | false | Show loading state |
| error | string | No | - | Error message to display |
| onClick | () => void | No | - | Click handler for interactive balances |
| compactNotation | boolean | No | false | Use compact notation (1.23K, 1.23M) |
| scientificNotation | boolean | No | false | Use scientific notation for small numbers |
| thousandsSeparator | string | No | ',' | Thousands separator character |
| decimalSeparator | string | No | '.' | Decimal separator character |
| className | string | No | - | Additional CSS classes |
| style | React.CSSProperties | No | - | 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
- Consistent Decimals: Use consistent decimal places for the same token type across your app
- Loading States: Always provide loading states for async balance data
- Error Handling: Gracefully handle and display balance loading errors
- Responsive Design: Ensure balance displays work on mobile devices
- Currency Symbols: Use standard currency symbols and token tickers
- 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
Related Components
- TokenBalance - Full-featured token balance with charts
- WalletOverview - Complete wallet interface
- CompactWalletOverview - Compact wallet display
- QuickDonateButton - Quick donation interface
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}
/>
);
}