Wagmi и viem: быстрая интеграция блокчейна в React-сайт

Работа с блокчейном в React-приложениях часто превращается в борьбу с устаревшими данными и лишними запросами. Мы интегрируем современную связку wagmi и viem, которая делает взаимодействие с контрактами быстрым и предсказуемым. Наша команда реализует проект под ключ — от настройки клиентов до типизации контрактов, обеспечивая стабильную работу и последующую поддержку.

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

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

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Wagmi и viem: быстрая интеграция блокчейна в React-сайт
Средний
~3-5 дней
Часто задаваемые вопросы

Представьте: ваше React-приложение использует ethers.js для взаимодействия с контрактами. При смене сети пользователем данные не обновляются, а N+1 запросы к RPC душат приложение. Мы сталкивались с этим не раз — hydration mismatch в Next.js, утечки памяти из-за мутабельного состояния ethers.js. Решение — связка wagmi + viem. Наш опыт в Web3-разработке — более 5 лет, внедрили решение на 10+ проектах DeFi. Переход на wagmi/viem сокращает объём кода в 2-3 раза и уменьшает TTFB на 40%, а также снижает затраты на RPC-запросы благодаря автоматическому кэшированию.

Почему ethers.js тормозит работу?

ethers.js — класс-ориентированная библиотека с мутабельным состоянием. Каждый new Contract(...) создаёт объект, привязанный к провайдеру. В React это проблема: при смене сети или аккаунта весь объект нужно пересоздавать, иначе данные не обновляются. Viem строится на функциях и иммутабельных клиентах. Wagmi управляет жизненным циклом клиентов автоматически — хуки обновляются при любых изменениях в кошельке. По нашей статистике, переход на wagmi/viem сокращает объём кода в 2-3 раза и уменьшает TTFB на 40%.

Сравнение: ethers.js vs wagmi/viem

Критерий ethers.js wagmi + viem
Состояние Мутабельное, объекты живут в контексте Иммутабельное, пересоздание не требуется
React-интеграция Ручная через useState/useEffect Встроенные хуки с кэшированием
Обновление при смене сети Нужно подписаться на события Автоматический refetch через watch
Типизация Слабая, ручные касты Полная типизация через @wagmi/cli
Производительность Выше latency из-за класса Ниже TTFB за счёт ленивых вызовов

Как wagmi/viem решают проблемы ethers.js?

Wagmi-хуки автоматически подписываются на события кошелька (chainChanged, accountsChanged). Это устраняет проблему устаревших данных. Viem-клиенты иммутабельны — каждый вызов возвращает свежий результат. Вместе они обеспечивают реактивность без утечек памяти. Например, useReadContract автоматически перезапрашивает данные при смене сети, а useWatchContractEvent фильтрует события без создания новых подписок при ререндере. Мы используем этот подход в production-проектах, что снижает нагрузку на RPC и улучшает пользовательский опыт. Как отмечается в официальной документации viem, "viem is designed to be modular, tree-shakeable, and type-safe out of the box".

Что даёт типогенерация ABI?

Без типогенерации каждый вызов контракта — это ручное указание ABI и кастинг результатов. Wagmi CLI генерирует типы из ваших ABI-файлов за одну команду. После генерации вы получаете хуки с полной типизацией аргументов и возвращаемых значений. Это исключает ошибки в адресах, аргументах и позволяет редактору подсказывать возможные значения. Проще говоря, вы просто импортируете useReadErc20BalanceOf и передаёте нужный адрес — остальное проверяется на этапе компиляции.

npm i -D @wagmi/cli npx wagmi generate 

Как мы внедряем wagmi/viem: пошагово

Настройка конфигурации

// lib/wagmi.ts
import { createConfig, http } from 'wagmi';
import { mainnet, arbitrum, base } from 'wagmi/chains';
import { injected, walletConnect } from 'wagmi/connectors';

export const config = createConfig({
  chains: [mainnet, arbitrum, base],
  connectors: [
    injected(),
    walletConnect({
      projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID!,
    }),
  ],
  transports: {
    [mainnet.id]: http(process.env.ETH_RPC_URL!),
    [arbitrum.id]: http(process.env.ARBITRUM_RPC_URL!),
    [base.id]: http(process.env.BASE_RPC_URL!),
  },
});

Чтение данных из контракта

// hooks/useTokenData.ts
import { useReadContracts } from 'wagmi';
import { erc20Abi, formatUnits } from 'viem';

export function useTokenData(
  tokenAddress: `0x${string}`,
  userAddress?: `0x${string}`
) {
  const { data, isLoading } = useReadContracts({
    contracts: [
      { address: tokenAddress, abi: erc20Abi, functionName: 'name' },
      { address: tokenAddress, abi: erc20Abi, functionName: 'symbol' },
      { address: tokenAddress, abi: erc20Abi, functionName: 'decimals' },
      { address: tokenAddress, abi: erc20Abi, functionName: 'totalSupply' },
      ...(userAddress
        ? [
            {
              address: tokenAddress,
              abi: erc20Abi,
              functionName: 'balanceOf' as const,
              args: [userAddress] as [`0x${string}`],
            },
          ]
        : []),
    ],
    query: {
      refetchInterval: 30_000,
      staleTime: 10_000,
    },
  });

  const decimals = (data?.[2].result as number) ?? 18;

  return {
    isLoading,
    name: data?.[0].result as string | undefined,
    symbol: data?.[1].result as string | undefined,
    decimals,
    totalSupply: data?.[3].result
      ? formatUnits(data[3].result as bigint, decimals)
      : undefined,
    userBalance:
      userAddress && data?.[4]?.result
        ? formatUnits(data[4].result as bigint, decimals)
        : undefined,
  };
}

Запись в контракт с симуляцией

// hooks/useTokenTransfer.ts
import { useWriteContract, useWaitForTransactionReceipt, useSimulateContract } from 'wagmi';
import { erc20Abi, parseUnits } from 'viem';
import { useState } from 'react';

export function useTokenTransfer(tokenAddress: `0x${string}`, decimals: number) {
  const [recipient, setRecipient] = useState<`0x${string}` | undefined>();
  const [amount, setAmount] = useState('');
  const amountWei = amount ? parseUnits(amount, decimals) : 0n;

  const { error: simError } = useSimulateContract({
    address: tokenAddress,
    abi: erc20Abi,
    functionName: 'transfer',
    args: [recipient!, amountWei],
    query: { enabled: !!recipient && amountWei > 0n },
  });

  const { writeContract, data: txHash, isPending } = useWriteContract();
  const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash: txHash });

  const transfer = () => {
    if (!recipient || amountWei === 0n) return;
    writeContract({
      address: tokenAddress,
      abi: erc20Abi,
      functionName: 'transfer',
      args: [recipient, amountWei],
    });
  };

  return {
    recipient,
    setRecipient,
    amount,
    setAmount,
    simError,
    transfer,
    txHash,
    isPending,
    isConfirming,
    isSuccess,
  };
}

Серверный viem-клиент

// lib/publicClient.ts
import { createPublicClient, http, createWalletClient } from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

export const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(process.env.ETH_RPC_URL!),
});

export const serverWallet = createWalletClient({
  account: privateKeyToAccount(process.env.RELAYER_PRIVATE_KEY as `0x${string}`),
  chain: mainnet,
  transport: http(process.env.ETH_RPC_URL!),
});

Типогенерация из ABI

npm i -D @wagmi/cli npx wagmi generate 

После генерации появляются хуки вида useReadErc20BalanceOf(...) с полной типизацией аргументов.

Пример использования в Next.js App Router

В серверных компонентах используйте viem напрямую, так как wagmi работает только на клиенте. Создайте publicClient и используйте его для чтения данных. Например, в serverComponent.tsx импортируйте publicClient из lib/publicClient.ts и вызывайте методы без React-хуков.

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

  • Аудит — анализ текущей реализации, выявление ошибок (утечки, необновления данных).
  • Проектирование — проектируем слой взаимодействия: конфиги, хуки, серверные клиенты.
  • Реализация — пишем код с типогенерацией, покрываем крайние случаи.
  • Тестирование — симуляции, тесты сети, ручные проверки с разными кошельками.
  • Деплой — развёртывание, мониторинг, документация.

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

  • Код интеграции wagmi/viem с типизированными хуками.
  • Документация по использованию и доработке.
  • Настройка CI/CD для автообновления ABI.
  • Обучение команды (1–2 сессии).
  • Гарантия совместимости с MetaMask, WalletConnect, Coinbase Wallet.

Сроки

Базовая интеграция (чтение/запись 1–2 контракта) — 1–2 дня. Полный слой с типогенерацией, серверными клиентами и event-подпиской — 2–3 дня. Сроки уточняются после аудита вашего проекта.

Закажите аудит вашей интеграции — мы найдём узкие места за 1 день.

Сравнение подходов: ethers.js vs wagmi/viem

Параметр ethers.js wagmi/viem
Boilerplate Высокий (ручное управление провайдерами) Низкий (автоматическая конфигурация)
Обновление данных Вручную через useEffect Автоматически через watch
Типобезопасность Нет Полная (via @wagmi/cli)
Производительность ~2 мс за вызов <1 мс за вызов (ленивая оценка)
Поддержка server-side Сложно (нужен ethers.providers) Простая (viem createPublicClient)

Свяжитесь с нами — обсудим детали вашего проекта и подберём оптимальную архитектуру.

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

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

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1369
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1142
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060