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

Вы интегрируете криптокошелёк в dApp и видите, что баланс USDC отображается как 0.000000000000000001 вместо 10. Причина — использован `formatEther` вместо `formatUnits` с 6 decimals. Или баланс обновляется только после перезагрузки страницы. Такие ошибки мы исправляем регулярно. Наш опыт в интеграци

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

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

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Реализация отображения баланса токенов на сайте
Средний
от 1 дня до 3 дней

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

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1422
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1288
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    984
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1250
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    987
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    1001

Вы интегрируете криптокошелёк в dApp и видите, что баланс USDC отображается как 0.000000000000000001 вместо 10. Причина — использован formatEther вместо formatUnits с 6 decimals. Или баланс обновляется только после перезагрузки страницы. Такие ошибки мы исправляем регулярно. Наш опыт в интеграции блокчейн-функционала — более 5 лет, реализовано более 50 проектов. В этой статье разберём, как правильно реализовать отображение баланса токенов: от нативного газа до портфеля из 20 ERC-20. Вы узнаете, как избежать типичных ошибок decimals, настроить realtime-обновление и оптимизировать RPC-запросы с помощью multicall. На примере реального кейса покажем, как собрать компонент баланса, который работает в mainnet и testnets.

Отображение баланса токенов: типичные проблемы и решения

  • Некорректные decimals: разные токены имеют разное количество знаков (USDC — 6, WBTC — 8, ETH — 18). Использование formatEther для USDC даёт 0.000000000000000001 вместо 10.
  • N+1 запросы: при отображении портфеля из 10 токенов без multicall делается 10 отдельных RPC-вызовов, что увеличивает TTFB и нагружает провайдера.
  • Устаревшие данные: баланс может не обновляться после транзакции. Нужна подписка на события или опрос по блокам.

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

Для нативного токена (ETH, MATIC, BNB) используем getBalance из viem. Для ERC-20 — контрактный вызов balanceOf. У каждого токена свой decimals, который нужно получать из контракта.

import { formatEther } from 'ethers'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; async function getNativeBalance(address: string): 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); } 

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

Для ERC-20 используем multicall, чтобы получить баланс и decimals одним запросом:

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 не опрашивает баланс. Нужно задать refetchInterval. Для Ethereum основной сети интервал 12 000 мс соответствует одному блоку. Альтернатива — подписка на событие Transfer смарт-контракта, но это требует дополнительной инфраструктуры. Мы рекомендуем refetchInterval для простоты и надёжности.

Пример компонента с автообновлением:

import { useBalance, useReadContract } from 'wagmi'; import { erc20Abi, formatUnits } from 'viem'; export function TokenBalance({ address, tokenAddress, symbol }) { const { data: nativeBalance } = useBalance({ address, query: { refetchInterval: 12_000 }, }); 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 }, }); // ... рендер } 

Форматирование баланса для UI

Сырое formatUnits возвращает строку с 18 знаками после запятой. Для отображения нужна логика. Используйте функцию formatUnits с последующим округлением.

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

Таблица примеров форматирования:

Токен Decimals Сырой баланс (bigint) Отформатированное значение
ETH 18 1000000000000000000 1.0 ETH
USDC 6 10000000 10.0 USDC
WBTC 8 500000000 5.0 WBTC

Избежание N+1 запросов с помощью multicall

Отметим: когда нужно показать portfolio — балансы 10–20 токенов — важно не делать 20 отдельных RPC-запросов. Multicall3 агрегирует все запросы в один. В viem есть встроенная функция multicall. Сравнение: последовательный вызов для 10 токенов занимает ~2 секунды (при стандартном RPC), а multicall — ~200 мс. Multicall лучше последовательных запросов в 10 раз.

Viem multicall documentation Таблица сравнения методов:

Метод Количество RPC-запросов Время выполнения Нагрузка на провайдера
Последовательные вызовы 10 ~2 секунды Высокая
Multicall 1 ~200 мс Низкая
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', args: [walletAddress], })); 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), })); } 

Использование multicall позволяет сократить затраты на RPC-инфраструктуру на 60–70%. Экономия времени разработки — до 2 дней при интеграции портфеля из 10 токенов. Это особенно заметно при высоких нагрузках.

Как ускорить загрузку портфеля токенов?

Используйте multicall для агрегации запросов и добавьте кэширование на стороне клиента. В wagmi можно настроить staleTime, чтобы избежать повторных запросов в течение нескольких секунд. Комбинируйте с refetchInterval для баланса между актуальностью и производительностью.

Изменение decimals у токена: обработка

Decimals токена обычно фиксированы, но могут меняться при апгрейде контракта. Подписывайтесь на события контракта или периодически проверяйте decimals через мультиколл. В подавляющем большинстве случаев decimals не меняются, но для критичных приложений стоит добавить fallback.

Процесс работы и сроки

  1. Анализ: определяем список токенов и сетей, выбираем провайдер RPC.
  2. Проектирование: архитектура компонентов, схема кэширования и обновления.
  3. Реализация: пишем хуки и компоненты, используем wagmi/viem.
  4. Тестирование: проверяем на mainnet-форке, с разными токенами и адресами.
  5. Деплой: развёртываем на продакшен, настраиваем мониторинг.

Что входит в работу

  • Готовая интеграция компонента отображения баланса (нативного + ERC-20).
  • Документация по конфигурации и использованию.
  • Доступ к RPC-провайдеру (можем предложить свой).
  • Обучение команды заказчика (1 час).
  • Гарантия отказоустойчивости и поддержка 2 недели после сдачи.

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

Стоимость рассчитывается индивидуально. Закажите оценку вашего проекта — мы рассчитаем точные сроки и стоимость за 1 день. Свяжитесь с нами для консультации.

Чек-лист для интеграции

  • [ ] Определить decimals для каждого токена (можно получить из контракта).
  • [ ] Использовать multicall для портфеля из 2+ токенов.
  • [ ] Настроить refetchInterval для автообновления (12 000 ms для Ethereum).
  • [ ] Форматировать вывод: не более 4 знаков, для малых сумм — экспонента.
  • [ ] Добавить skeleton-загрузку и обработку ошибок.