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

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, 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, realtime-обновления, мультисетевой контекст, кэш с инвалидацией. Разбираем от простого к сложному.

Получение баланса нативного токена

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,
  });
}

Отображение нескольких токенов

Когда нужно показать portfolio — балансы 10–20 токенов — важно не делать 20 отдельных RPC-запросов. multicall3 контракт (deployed на большинстве 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),
  }));
}

Срок реализации: один токен с обновлением в реальном времени — половина дня. Полноценный portfolio-виджет с несколькими токенами, форматированием, skeleton-загрузкой и обновлением по блокам — 1–2 дня.