Реалізація відображення балансу токенів на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація відображення балансу токенів на сайті
Середня
від 1 робочого дня до 3 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація відображення балансу токенів на веб-сайті

Показати баланс токена — три рядки коду. Показати його правильно — це вже інша історія: форматування з урахуванням decimals, оновлення в реальному часі, контекст мультичейну, кеш з інвалідацією. Давайте йти від простого до складного.

Отримання балансу нативного токена

import { formatEther } from 'ethers';
import { usePublicClient } from 'wagmi';

async function getNativeBalance(address: string, chainId: number): Promise<string> {
  const client = createPublicClient({
    chain: mainnet,
    transport: http(process.env.ETH_RPC_URL),
  });

  const balance = await client.getBalance({ address: address as `0x${string}` });
  return formatEther(balance); // "1.234567890123456789"
}

getBalance повертає bigint у wei. formatEther ділить на 10^18. Для відображення зазвичай потрібно округлити до 4–6 значущих цифр.

Баланс ERC-20 з урахуванням decimals

Різні токени мають різну кількість decimals: USDC — 6, більшість ERC-20 — 18, WBTC — 8. Використання formatEther для USDC дасть неправильний результат.

import { erc20Abi, formatUnits } from 'viem';

async function getTokenBalance(
  tokenAddress: `0x${string}`,
  walletAddress: `0x${string}`,
  client: PublicClient,
): Promise<{ formatted: string; raw: bigint; decimals: number }> {
  const [balance, decimals] = await client.multicall({
    contracts: [
      {
        address: tokenAddress,
        abi: erc20Abi,
        functionName: 'balanceOf',
        args: [walletAddress],
      },
      {
        address: tokenAddress,
        abi: erc20Abi,
        functionName: 'decimals',
      },
    ],
  });

  const raw = balance.result as bigint;
  const dec = decimals.result as number;

  return {
    raw,
    decimals: dec,
    formatted: formatUnits(raw, dec),
  };
}

multicall — один RPC-виклик замість двох. На проектах із 10+ токенами це критично для продуктивності.

Компонент з автоматичним оновленням через wagmi

// components/TokenBalance.tsx
import { useBalance, useReadContract } from 'wagmi';
import { erc20Abi, formatUnits } from 'viem';

interface TokenBalanceProps {
  address: `0x${string}`;
  tokenAddress?: `0x${string}`; // якщо не встановлено — показати нативний токен
  symbol?: string;
}

export function TokenBalance({ address, tokenAddress, symbol }: TokenBalanceProps) {
  // Нативний баланс
  const { data: nativeBalance } = useBalance({
    address,
    query: { refetchInterval: 12_000 }, // оновлення кожен блок (~12s на Ethereum)
  });

  // Баланс ERC-20
  const { data: tokenBalance } = useReadContract({
    address: tokenAddress,
    abi: erc20Abi,
    functionName: 'balanceOf',
    args: [address],
    query: { enabled: !!tokenAddress, refetchInterval: 12_000 },
  });

  const { data: decimals } = useReadContract({
    address: tokenAddress,
    abi: erc20Abi,
    functionName: 'decimals',
    query: { enabled: !!tokenAddress, staleTime: Infinity }, // decimals не змінюються
  });

  if (tokenAddress && tokenBalance != null && decimals != null) {
    const formatted = formatUnits(tokenBalance as bigint, decimals as number);
    return <BalanceDisplay value={formatted} symbol={symbol ?? 'TOKEN'} />;
  }

  if (nativeBalance) {
    return <BalanceDisplay value={nativeBalance.formatted} symbol={nativeBalance.symbol} />;
  }

  return <BalanceSkeleton />;
}

Форматування для UI

Сирий formatUnits повертає рядок з 18 знаками після коми. Для відображення потрібна логіка:

export function formatTokenAmount(
  value: string | bigint,
  decimals: number,
  opts?: { maxDecimals?: number; compact?: boolean },
): string {
  const raw = typeof value === 'bigint' ? formatUnits(value, decimals) : value;
  const num = parseFloat(raw);

  if (num === 0) return '0';

  const maxDec = opts?.maxDecimals ?? 4;

  if (opts?.compact && num >= 1_000_000) {
    return `${(num / 1_000_000).toFixed(2)}M`;
  }
  if (opts?.compact && num >= 1_000) {
    return `${(num / 1_000).toFixed(2)}K`;
  }

  // Для дуже малих значень — показати значущі цифри
  if (num < 0.0001 && num > 0) {
    return num.toExponential(2);
  }

  return num.toLocaleString('en-US', {
    maximumFractionDigits: maxDec,
    minimumFractionDigits: 0,
  });
}

Відображення кількох токенів

Коли потрібно показати портфель — баланси 10–20 токенів — важливо не робити 20 окремих RPC-запитів. Контракт multicall3 (розгорнутий на більшості EVM-мереж) агрегує всі запити в один:

import { multicall } from 'viem/actions';

async function getPortfolioBalances(
  tokens: Array<{ address: `0x${string}`; decimals: number; symbol: string }>,
  walletAddress: `0x${string}`,
  client: PublicClient,
) {
  const calls = tokens.map(token => ({
    address: token.address,
    abi: erc20Abi,
    functionName: 'balanceOf' as const,
    args: [walletAddress] as [`0x${string}`],
  }));

  const results = await multicall(client, { contracts: calls });

  return tokens.map((token, i) => ({
    ...token,
    balance: results[i].result as bigint,
    formatted: formatUnits(results[i].result as bigint, token.decimals),
  }));
}

Часова шкала: один токен з оновленнями в реальному часі — половина дня. Повноцінний портфельний віджет із кількома токенами, форматуванням, завантаженням скелету та оновленнями блоку — 1–2 дні.