Skip to main content

useBalances Hook

The useBalances hook provides reactive balance data with automatic formatting.

Basic Usage

import { useBalances } from '@testinprod-io/privacy-boost-react';

function BalanceDisplay() {
  const { balances, loading, lastSynced } = useBalances();

  if (loading) return <div>Loading...</div>;

  return (
    <ul>
      {balances.map((balance) => (
        <li key={balance.tokenAddress}>
          {balance.symbol}: {balance.formattedShielded}
        </li>
      ))}
    </ul>
  );
}

Return Value

interface UseBalancesResult {
  // Reactive balance data
  balances: FormattedBalance[];
  loading: boolean;
  lastSynced: number | null;

  // Query functions
  getBalance(tokenAddress: string): FormattedBalance | undefined;
  getShieldedBalance(tokenAddress: string): bigint;
  getWalletBalance(tokenAddress: string): bigint;

  // Computed values
  totalShielded: bigint;
  tokenCount: number;
}

FormattedBalance Type

interface FormattedBalance extends TokenBalance {
  tokenAddress: Hex;
  shielded: bigint;
  wallet: bigint;
  symbol?: string;
  decimals?: number;

  // Pre-formatted strings
  formattedShielded: string;  // e.g., "1.5"
  formattedWallet: string;    // e.g., "2.3"
}

State Properties

PropertyTypeDescription
balancesFormattedBalance[]All token balances
loadingbooleanCurrently syncing
lastSyncednumber | nullLast sync timestamp
totalShieldedbigintSum of all shielded balances
tokenCountnumberNon-zero token count

Query Functions

getBalance(tokenAddress)

Get formatted balance for a specific token:
const { getBalance } = useBalances();

const usdcBalance = getBalance('0x...usdc');
if (usdcBalance) {
  console.log(`USDC: ${usdcBalance.formattedShielded}`);
}

getShieldedBalance(tokenAddress)

Get raw shielded balance:
const { getShieldedBalance } = useBalances();

const shielded = getShieldedBalance('0x...usdc');
// Returns: bigint (e.g., 1500000n for 1.5 USDC)

getWalletBalance(tokenAddress)

Get raw wallet balance:
const { getWalletBalance } = useBalances();

const wallet = getWalletBalance('0x...usdc');

Examples

Balance List

function BalanceList() {
  const { balances, loading, totalShielded, tokenCount } = useBalances();

  if (loading) {
    return <div>Loading balances...</div>;
  }

  if (balances.length === 0) {
    return <div>No tokens found</div>;
  }

  return (
    <div>
      <h3>{tokenCount} tokens</h3>
      <table>
        <thead>
          <tr>
            <th>Token</th>
            <th>Shielded</th>
            <th>Wallet</th>
          </tr>
        </thead>
        <tbody>
          {balances.map((b) => (
            <tr key={b.tokenAddress}>
              <td>{b.symbol}</td>
              <td>{b.formattedShielded}</td>
              <td>{b.formattedWallet}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Single Token Balance

function TokenBalance({ tokenAddress }: { tokenAddress: string }) {
  const { getBalance } = useBalances();
  const balance = getBalance(tokenAddress);

  if (!balance) {
    return <span>-</span>;
  }

  return (
    <span>
      {balance.formattedShielded} {balance.symbol}
    </span>
  );
}

Balance Summary Card

function BalanceSummary() {
  const { balances, totalShielded, loading } = useBalances();

  // Filter non-zero balances
  const nonZeroBalances = balances.filter(
    (b) => b.shielded > 0n || b.wallet > 0n
  );

  return (
    <div className="balance-summary">
      {loading && <span className="syncing">Syncing...</span>}

      <h4>Your Privacy Balance</h4>
      {nonZeroBalances.length === 0 ? (
        <p>No tokens. Deposit to get started.</p>
      ) : (
        nonZeroBalances.map((b) => (
          <div key={b.tokenAddress} className="balance-item">
            <span className="symbol">{b.symbol}</span>
            <span className="amount">{b.formattedShielded}</span>
          </div>
        ))
      )}
    </div>
  );
}

Balance with Refresh

function RefreshableBalance() {
  const { balances, loading, lastSynced } = useBalances();
  const { syncAllBalances } = useVault();
  const [refreshing, setRefreshing] = useState(false);

  const handleRefresh = async () => {
    setRefreshing(true);
    await syncAllBalances();
    setRefreshing(false);
  };

  return (
    <div>
      <div className="header">
        <h3>Balances</h3>
        <button onClick={handleRefresh} disabled={refreshing || loading}>
          {refreshing ? 'Syncing...' : 'Refresh'}
        </button>
      </div>

      {lastSynced && (
        <small>
          Last updated: {new Date(lastSynced).toLocaleTimeString()}
        </small>
      )}

      <ul>
        {balances.map((b) => (
          <li key={b.tokenAddress}>
            {b.symbol}: {b.formattedShielded}
          </li>
        ))}
      </ul>
    </div>
  );
}

Conditional Rendering

function DepositPrompt({ tokenAddress }: { tokenAddress: string }) {
  const { getShieldedBalance } = useBalances();
  const balance = getShieldedBalance(tokenAddress);

  if (balance > 0n) {
    return null;
  }

  return (
    <div className="deposit-prompt">
      <p>You have no shielded tokens.</p>
      <button>Make your first deposit</button>
    </div>
  );
}

Polling for Updates

function AutoRefreshBalances() {
  const { syncAllBalances } = useVault();

  useEffect(() => {
    // Refresh every 30 seconds
    const interval = setInterval(() => {
      syncAllBalances();
    }, 30000);

    return () => clearInterval(interval);
  }, [syncAllBalances]);

  return <BalanceList />;
}

Next Steps