> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usemina.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Token Selection

> Build chain and token selection UI components with the Mina SDK

# Token Selection

This example demonstrates how to build React components for selecting source chains and tokens, including filtering tokens by balance.

## Overview

A bridge interface typically needs:

1. A chain selector dropdown
2. A token selector with search and balance display
3. State management to connect selections to the quote system

## Chain Selection Component

This component displays available source chains with logos and names.

<CodeGroup>
  ```tsx ChainSelector.tsx theme={null}
  import { useState, useEffect } from 'react';
  import { useMina } from '@siphoyawe/mina-sdk/react';
  import type { Chain } from '@siphoyawe/mina-sdk/react';

  interface ChainSelectorProps {
    selectedChainId: number | null;
    onSelect: (chain: Chain) => void;
  }

  export function ChainSelector({ selectedChainId, onSelect }: ChainSelectorProps) {
    const { mina, isReady } = useMina();
    const [chains, setChains] = useState<Chain[]>([]);
    const [isLoading, setIsLoading] = useState(false);
    const [isOpen, setIsOpen] = useState(false);

    // Fetch available chains on mount
    useEffect(() => {
      if (!mina || !isReady) return;

      async function fetchChains() {
        setIsLoading(true);
        try {
          // Get chains with valid routes to HyperEVM
          const availableChains = await mina.getChainsByRoutes();
          setChains(availableChains);
        } catch (error) {
          console.error('Failed to fetch chains:', error);
        } finally {
          setIsLoading(false);
        }
      }

      fetchChains();
    }, [mina, isReady]);

    // Find the selected chain object
    const selectedChain = chains.find((c) => c.id === selectedChainId);

    if (isLoading) {
      return (
        <div className="chain-selector loading">
          <span>Loading chains...</span>
        </div>
      );
    }

    return (
      <div className="chain-selector">
        {/* Selected Chain Display / Trigger Button */}
        <button
          className="chain-selector-trigger"
          onClick={() => setIsOpen(!isOpen)}
          type="button"
        >
          {selectedChain ? (
            <>
              <img
                src={selectedChain.logoUrl}
                alt={selectedChain.name}
                className="chain-logo"
              />
              <span>{selectedChain.name}</span>
            </>
          ) : (
            <span>Select a chain</span>
          )}
          <ChevronDownIcon />
        </button>

        {/* Dropdown List */}
        {isOpen && (
          <div className="chain-selector-dropdown">
            {chains.map((chain) => (
              <button
                key={chain.id}
                className={`chain-option ${chain.id === selectedChainId ? 'selected' : ''}`}
                onClick={() => {
                  onSelect(chain);
                  setIsOpen(false);
                }}
                type="button"
              >
                <img
                  src={chain.logoUrl}
                  alt={chain.name}
                  className="chain-logo"
                />
                <span className="chain-name">{chain.name}</span>
                <span className="chain-id">ID: {chain.id}</span>
              </button>
            ))}
          </div>
        )}
      </div>
    );
  }

  // Simple chevron icon component
  function ChevronDownIcon() {
    return (
      <svg
        width="16"
        height="16"
        viewBox="0 0 16 16"
        fill="currentColor"
      >
        <path d="M4 6l4 4 4-4" stroke="currentColor" strokeWidth="2" fill="none" />
      </svg>
    );
  }
  ```

  ```css ChainSelector.css theme={null}
  .chain-selector {
    position: relative;
    width: 100%;
  }

  .chain-selector-trigger {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 12px 16px;
    background: #1a1a2e;
    border: 1px solid #2d2d44;
    border-radius: 12px;
    color: #ffffff;
    cursor: pointer;
    font-size: 14px;
  }

  .chain-selector-trigger:hover {
    border-color: #7dd3fc;
  }

  .chain-logo {
    width: 24px;
    height: 24px;
    border-radius: 50%;
  }

  .chain-selector-dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin-top: 4px;
    background: #1a1a2e;
    border: 1px solid #2d2d44;
    border-radius: 12px;
    max-height: 300px;
    overflow-y: auto;
    z-index: 100;
  }

  .chain-option {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 12px 16px;
    background: transparent;
    border: none;
    color: #ffffff;
    cursor: pointer;
    text-align: left;
  }

  .chain-option:hover {
    background: #2d2d44;
  }

  .chain-option.selected {
    background: #0ea5e9;
  }

  .chain-name {
    flex: 1;
  }

  .chain-id {
    color: #888;
    font-size: 12px;
  }
  ```
</CodeGroup>

## Token Selection Component

This component displays available tokens for a selected chain with balance information.

<CodeGroup>
  ```tsx TokenSelector.tsx theme={null}
  import { useState, useEffect, useMemo } from 'react';
  import { useMina, useTokenBalance } from '@siphoyawe/mina-sdk/react';
  import type { Token } from '@siphoyawe/mina-sdk/react';

  interface TokenSelectorProps {
    chainId: number | null;
    walletAddress: string | null;
    selectedToken: Token | null;
    onSelect: (token: Token) => void;
    showOnlyWithBalance?: boolean;
  }

  export function TokenSelector({
    chainId,
    walletAddress,
    selectedToken,
    onSelect,
    showOnlyWithBalance = false,
  }: TokenSelectorProps) {
    const { mina, isReady } = useMina();
    const [tokens, setTokens] = useState<Token[]>([]);
    const [balances, setBalances] = useState<Record<string, string>>({});
    const [isLoading, setIsLoading] = useState(false);
    const [isOpen, setIsOpen] = useState(false);
    const [searchQuery, setSearchQuery] = useState('');

    // Fetch tokens when chain changes
    useEffect(() => {
      if (!mina || !isReady || !chainId) {
        setTokens([]);
        return;
      }

      async function fetchTokens() {
        setIsLoading(true);
        try {
          // Get bridgeable tokens (only tokens that can bridge to HyperEVM)
          const bridgeableTokens = await mina.getBridgeableTokens(chainId);
          setTokens(bridgeableTokens);
        } catch (error) {
          console.error('Failed to fetch tokens:', error);
        } finally {
          setIsLoading(false);
        }
      }

      fetchTokens();
    }, [mina, isReady, chainId]);

    // Fetch balances for all tokens
    useEffect(() => {
      if (!mina || !isReady || !chainId || !walletAddress || tokens.length === 0) {
        return;
      }

      async function fetchBalances() {
        const balanceMap: Record<string, string> = {};

        // Fetch balances in parallel (batch of 5 at a time)
        const batchSize = 5;
        for (let i = 0; i < tokens.length; i += batchSize) {
          const batch = tokens.slice(i, i + batchSize);
          const results = await Promise.allSettled(
            batch.map((token) =>
              mina.getBalance(chainId, token.address, walletAddress)
            )
          );

          results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
              balanceMap[batch[index].address] = result.value.formatted;
            }
          });
        }

        setBalances(balanceMap);
      }

      fetchBalances();
    }, [mina, isReady, chainId, walletAddress, tokens]);

    // Filter and sort tokens
    const filteredTokens = useMemo(() => {
      let filtered = tokens;

      // Filter by search query
      if (searchQuery) {
        const query = searchQuery.toLowerCase();
        filtered = filtered.filter(
          (token) =>
            token.symbol.toLowerCase().includes(query) ||
            token.name.toLowerCase().includes(query)
        );
      }

      // Filter to only tokens with balance
      if (showOnlyWithBalance) {
        filtered = filtered.filter((token) => {
          const balance = balances[token.address];
          return balance && parseFloat(balance) > 0;
        });
      }

      // Sort by balance (tokens with balance first, then alphabetically)
      return filtered.sort((a, b) => {
        const balanceA = parseFloat(balances[a.address] || '0');
        const balanceB = parseFloat(balances[b.address] || '0');

        if (balanceA > 0 && balanceB === 0) return -1;
        if (balanceA === 0 && balanceB > 0) return 1;
        if (balanceA !== balanceB) return balanceB - balanceA;

        return a.symbol.localeCompare(b.symbol);
      });
    }, [tokens, searchQuery, showOnlyWithBalance, balances]);

    if (!chainId) {
      return (
        <div className="token-selector disabled">
          <span>Select a chain first</span>
        </div>
      );
    }

    return (
      <div className="token-selector">
        {/* Selected Token Display */}
        <button
          className="token-selector-trigger"
          onClick={() => setIsOpen(!isOpen)}
          type="button"
          disabled={isLoading}
        >
          {selectedToken ? (
            <>
              <img
                src={selectedToken.logoUrl}
                alt={selectedToken.symbol}
                className="token-logo"
              />
              <div className="token-info">
                <span className="token-symbol">{selectedToken.symbol}</span>
                {balances[selectedToken.address] && (
                  <span className="token-balance">
                    {balances[selectedToken.address]}
                  </span>
                )}
              </div>
            </>
          ) : (
            <span>{isLoading ? 'Loading tokens...' : 'Select token'}</span>
          )}
        </button>

        {/* Dropdown with Search */}
        {isOpen && (
          <div className="token-selector-dropdown">
            {/* Search Input */}
            <div className="token-search">
              <input
                type="text"
                placeholder="Search tokens..."
                value={searchQuery}
                onChange={(e) => setSearchQuery(e.target.value)}
                autoFocus
              />
            </div>

            {/* Token List */}
            <div className="token-list">
              {filteredTokens.length === 0 ? (
                <div className="no-tokens">
                  {searchQuery ? 'No tokens match your search' : 'No tokens available'}
                </div>
              ) : (
                filteredTokens.map((token) => (
                  <TokenOption
                    key={token.address}
                    token={token}
                    balance={balances[token.address]}
                    isSelected={selectedToken?.address === token.address}
                    onSelect={() => {
                      onSelect(token);
                      setIsOpen(false);
                      setSearchQuery('');
                    }}
                  />
                ))
              )}
            </div>
          </div>
        )}
      </div>
    );
  }

  // Individual token option component
  interface TokenOptionProps {
    token: Token;
    balance?: string;
    isSelected: boolean;
    onSelect: () => void;
  }

  function TokenOption({ token, balance, isSelected, onSelect }: TokenOptionProps) {
    const hasBalance = balance && parseFloat(balance) > 0;

    return (
      <button
        className={`token-option ${isSelected ? 'selected' : ''} ${hasBalance ? 'has-balance' : ''}`}
        onClick={onSelect}
        type="button"
      >
        <img src={token.logoUrl} alt={token.symbol} className="token-logo" />
        <div className="token-details">
          <span className="token-symbol">{token.symbol}</span>
          <span className="token-name">{token.name}</span>
        </div>
        <div className="token-balance-info">
          {balance ? (
            <>
              <span className="balance-amount">{formatBalance(balance)}</span>
              {token.priceUsd && (
                <span className="balance-usd">
                  ${(parseFloat(balance) * token.priceUsd).toFixed(2)}
                </span>
              )}
            </>
          ) : (
            <span className="balance-loading">...</span>
          )}
        </div>
      </button>
    );
  }

  // Helper to format balance display
  function formatBalance(balance: string): string {
    const num = parseFloat(balance);
    if (num === 0) return '0';
    if (num < 0.0001) return '<0.0001';
    if (num < 1) return num.toFixed(4);
    if (num < 1000) return num.toFixed(2);
    if (num < 1000000) return `${(num / 1000).toFixed(2)}K`;
    return `${(num / 1000000).toFixed(2)}M`;
  }
  ```

  ```css TokenSelector.css theme={null}
  .token-selector {
    position: relative;
    width: 100%;
  }

  .token-selector.disabled {
    opacity: 0.5;
    pointer-events: none;
  }

  .token-selector-trigger {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 12px 16px;
    background: #1a1a2e;
    border: 1px solid #2d2d44;
    border-radius: 12px;
    color: #ffffff;
    cursor: pointer;
  }

  .token-logo {
    width: 32px;
    height: 32px;
    border-radius: 50%;
  }

  .token-info {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }

  .token-symbol {
    font-weight: 600;
  }

  .token-balance {
    font-size: 12px;
    color: #888;
  }

  .token-selector-dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin-top: 4px;
    background: #1a1a2e;
    border: 1px solid #2d2d44;
    border-radius: 12px;
    max-height: 400px;
    overflow: hidden;
    z-index: 100;
  }

  .token-search {
    padding: 12px;
    border-bottom: 1px solid #2d2d44;
  }

  .token-search input {
    width: 100%;
    padding: 10px 12px;
    background: #0d0d1a;
    border: 1px solid #2d2d44;
    border-radius: 8px;
    color: #ffffff;
    font-size: 14px;
  }

  .token-search input:focus {
    outline: none;
    border-color: #7dd3fc;
  }

  .token-list {
    max-height: 320px;
    overflow-y: auto;
  }

  .token-option {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 12px 16px;
    background: transparent;
    border: none;
    color: #ffffff;
    cursor: pointer;
    text-align: left;
  }

  .token-option:hover {
    background: #2d2d44;
  }

  .token-option.selected {
    background: #0ea5e9;
  }

  .token-option.has-balance {
    background: rgba(125, 211, 252, 0.1);
  }

  .token-details {
    flex: 1;
    display: flex;
    flex-direction: column;
  }

  .token-name {
    font-size: 12px;
    color: #888;
  }

  .token-balance-info {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
  }

  .balance-amount {
    font-weight: 500;
  }

  .balance-usd {
    font-size: 12px;
    color: #888;
  }

  .no-tokens {
    padding: 24px;
    text-align: center;
    color: #888;
  }
  ```
</CodeGroup>

## Using with State Management

Here's how to combine these components with React state:

```tsx BridgeForm.tsx theme={null}
import { useState } from 'react';
import { MinaProvider } from '@siphoyawe/mina-sdk/react';
import type { Chain, Token } from '@siphoyawe/mina-sdk/react';
import { ChainSelector } from './ChainSelector';
import { TokenSelector } from './TokenSelector';
import { useAccount } from 'wagmi'; // Or your wallet library

function BridgeForm() {
  const { address: walletAddress } = useAccount();

  // State for selections
  const [sourceChain, setSourceChain] = useState<Chain | null>(null);
  const [sourceToken, setSourceToken] = useState<Token | null>(null);
  const [amount, setAmount] = useState('');

  // Reset token when chain changes
  const handleChainChange = (chain: Chain) => {
    setSourceChain(chain);
    setSourceToken(null); // Clear token selection
    setAmount('');
  };

  return (
    <div className="bridge-form">
      <div className="form-section">
        <label>From Chain</label>
        <ChainSelector
          selectedChainId={sourceChain?.id ?? null}
          onSelect={handleChainChange}
        />
      </div>

      <div className="form-section">
        <label>Token</label>
        <TokenSelector
          chainId={sourceChain?.id ?? null}
          walletAddress={walletAddress ?? null}
          selectedToken={sourceToken}
          onSelect={setSourceToken}
          showOnlyWithBalance={false}
        />
      </div>

      <div className="form-section">
        <label>Amount</label>
        <input
          type="number"
          placeholder="0.00"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
          disabled={!sourceToken}
        />
      </div>

      {/* Display selected values */}
      {sourceChain && sourceToken && amount && (
        <div className="selection-summary">
          Bridging {amount} {sourceToken.symbol} from {sourceChain.name} to HyperEVM
        </div>
      )}
    </div>
  );
}

// Wrap with MinaProvider at app level
export function App() {
  return (
    <MinaProvider config={{ integrator: 'my-app' }}>
      <BridgeForm />
    </MinaProvider>
  );
}
```

## Using the useTokenBalance Hook

For individual token balance display, use the `useTokenBalance` hook:

```tsx SingleTokenBalance.tsx theme={null}
import { useTokenBalance } from '@siphoyawe/mina-sdk/react';

interface BalanceDisplayProps {
  chainId: number;
  tokenAddress: string;
  walletAddress: string;
}

export function BalanceDisplay({ chainId, tokenAddress, walletAddress }: BalanceDisplayProps) {
  const {
    formattedBalance,
    symbol,
    balanceUsd,
    isLoading,
    error,
    refetch,
  } = useTokenBalance({
    chainId,
    tokenAddress,
    walletAddress,
    refetchInterval: 15000, // Refresh every 15 seconds
  });

  if (isLoading && !formattedBalance) {
    return <span className="balance">Loading...</span>;
  }

  if (error) {
    return (
      <span className="balance error">
        Error loading balance
        <button onClick={refetch}>Retry</button>
      </span>
    );
  }

  return (
    <div className="balance-display">
      <span className="balance">
        {formattedBalance ?? '0'} {symbol}
      </span>
      {balanceUsd !== null && (
        <span className="balance-usd">${balanceUsd.toFixed(2)}</span>
      )}
    </div>
  );
}
```

## Best Practices

<Note>
  When building token selectors, consider these UX best practices:

  1. Show tokens with balances at the top of the list
  2. Include token logos for visual recognition
  3. Display both symbol and name for clarity
  4. Show USD values when available
  5. Add search functionality for chains with many tokens
  6. Disable the token selector until a chain is selected
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Quote Display" icon="receipt" href="/examples/quote-display">
    Show quote details and fees
  </Card>

  <Card title="Full Integration" icon="puzzle-piece" href="/examples/full-integration">
    Complete bridge widget
  </Card>
</CardGroup>
