Wagmi: React API для работы с кошельком
Оглавление курса «Web3-интерфейс: реестр в браузере» · Урок 27 из 30.
Wagmi связывает React-компоненты с клиентами Ethereum и подключением кошелька, а TanStack Query управляет запросами и кешем. Эти библиотеки полезны, если сохраняют уже понятные нам границы. В этом уроке создадим отдельный закреплённый профиль и прочитаем курс 17 через hook. Результат — API одной версии не смешивается с примерами другого поколения.
Профиль: wagmi 2.16.9, viem 2.37.9, React и React DOM 19.1.1, TanStack Query 5.85.5. Публичные package-манифесты подтверждают совместимые диапазоны peerDependencies; установка и runtime-проверка не выполнялись. Наличие совместимых деклараций не является отчётом о собранном проекте. Современная документация уже обсуждает wagmi v3, поэтому для этой лаборатории явно сохраняем v2.
Конфигурация приложения
Создадим wagmi-config.js, используя ранее описанную local. Коннектор является возможностью подключения, а не обязательным шагом чтения. Список поддерживаемых сетей остаётся ограниченным локальным профилем.
import { createConfig, http } from 'wagmi';
import { injected } from 'wagmi/connectors';
import { local } from './client.js';
export const config = createConfig({
chains: [local],
connectors: [injected()],
transports: { [local.id]: http('http://127.0.0.1:8545') },
});
Для первоначального DOM-маршрута мы вручную обнаруживали несколько провайдеров. В React-линии конфигурация коннекторов берёт часть этой ответственности. Это альтернативное представление, а не необходимость одновременно создавать собственные и библиотечные подключения для одного действия. Фактический список и UI выбора нужно рассмотреть в отдельной оболочке приложения.
На верхнем уровне создаётся один QueryClient, а не новый объект при каждом рендере. Затем WagmiProvider и QueryClientProvider предоставляют конфигурацию компонентам. Полный файл оболочки ниже предполагает существующий корневой рендер React, который не приводится повторно.
import { WagmiProvider } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { config } from './wagmi-config.js';
const queryClient = new QueryClient();
export function LibraryProviders({ children }) {
return <WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</WagmiProvider>;
}
Порядок и владение провайдерами понятны: конфигурация сети стабильна для приложения, кеш разделяется его карточками. Создание нового кеша внутри функции без устойчивого хранения сбрасывало бы запросы и мешало переиспользованию. Серверный рендер добавит другие требования к изоляции экземпляров между запросами; в этом уроке рассматривается браузерная локальная линия.
Чтение не требует подключения
Компонент использует useReadContract с явным chainId, адресом и ABI. Аккаунт для публичного getter не передаётся. Адрес поступает из нормализованной конфигурации, как в предыдущих уроках.
import { useReadContract, useAccount } from 'wagmi';
import { catalogAbi } from './catalog-abi.js';
export function WagmiCourse({ address }) {
const account = useAccount(); // Именно API wagmi v2 выбранного профиля.
const query = useReadContract({
chainId: 31337,
address,
abi: catalogAbi,
functionName: 'courses',
args: [17n],
});
return <section>
<h2>Курс 17</h2>
{query.isPending && <p role="status">Читаем реестр…</p>}
{query.isError && <p role="status">Чтение не завершено.</p>}
{query.data && <p>{query.data[1] ? 'Активен' : 'Неактивен'}: {query.data[0]}</p>}
<p>{account.isConnected ? 'Кошелёк подключён' : 'Чтение без кошелька'}</p>
<button type="button" onClick={() => query.refetch()}>Обновить</button>
</section>;
}
В выбранной версии useAccount является действующим экспортом. В wagmi v3 документация использует новую модель, включая переименование в useConnection. Нельзя заменить одно имя из свежего примера и оставить зависимости v2, надеясь на автоматическую совместимость. Миграция рассматривается как отдельное изменение конфигурации, типов и поведения.
Полученная пара всё ещё требует семантической классификации нулевого отпечатка. Короткий JSX показывает место данных, а полный компонент использует нормализованную карточку, как раньше. Hook не превращает неактивную запись в отсутствующую и не объясняет редакционную причину деактивации.
Свежесть кеша
Библиотечный кеш уменьшает дублирование запросов, но не делает состояние вечным. Настройки времени устаревания, обновления при фокусе и повторов должны соответствовать задаче. Для формы записи предварительное моделирование остаётся отдельным шагом. Наличие данных hook не является разрешением подписывать.
Ключи запросов библиотеки формируются на основе параметров. Если приложение добавляет внешнюю историю окружения, следует учесть её при замене локальной лаборатории и очистке кеша. Один и тот же chainId и адрес после сброса узла могут описывать новую историю. Реактивные hooks не знают этого прикладного правила автоматически.
Запись и явное согласие
Возможность useWriteContract не означает, что отправка должна происходить в эффекте. Кнопка пользователя начинает маршрут, подготовленный вызов связан с текущим аккаунтом, а receipt наблюдается отдельно. При смене сети и адреса подключение инвалидирует намерение по уже изученным правилам.
Для упражнения выпишите названия hooks и провайдеров выбранного профиля, затем сравните их с документом миграции v3. Ожидается две отдельные согласованные конфигурации, а не смесь импортов. После этого объясните, почему отключённый кошелёк не препятствует useReadContract. Источники: репозиторий wagmi версии2.16.9, миграция v2→v3 и Query Keys.
Зависимости и профиль окружения
Декларации peerDependencies проверяют разрешённые диапазоны, но не фиксируют весь граф пакетов. После отдельной разрешённой установки lockfile станет записью конкретно выбранных транзитивных зависимостей. Сейчас такого запуска не было, поэтому нельзя объявлять граф воспроизводимым только по четырём верхним версиям. Мы фиксируем исходный учебный профиль и сохраняем границу будущего подтверждения.
Для этого профиля используется TypeScript не ниже диапазона, объявленного пакетом wagmi. Если приложение написано JavaScript, редактор всё равно может получать библиотечные типы. При превращении примера в TypeScript-проект версия компилятора также фиксируется отдельно. Нельзя взять случайную современную конфигурацию сборщика и считать её проверенной частью этой лаборатории.
Управление обновлением
Когда событие публикации сообщает об изменении курса, query можно инвалидировать или явно перечитать. Важно выбрать конкретный ключ и сохранить введённый текст формы. Полная очистка всего кеша после любого события создаёт лишнюю нагрузку и скачки состояния соседних карточек. Для курса17 обновляется чтение его реестра в нужной сети.
Повтор при фокусе окна полезен для карточки, но не должен отправлять запись. Mutation и пользовательская операция имеют самостоятельный маршрут. Хеш и receipt сохраняются в истории, которая живёт дольше компонента. Hook упрощает вызов API, а приложение продолжает отвечать за смысл состояний и восстановление.
Если число сетей расширяется, каждой нужна запись deployment и transport. useAccount сообщает о подключении, но сам не выбирает правильный контракт. Для страницы конкретного реестра поддерживаемый chainId у чтения остаётся явным. Иначе смена кошелька могла бы заставить hook читать тот же адрес в чужой цепочке.
Что сохраняется при переносе
Составленный ранее ABI, правила проверки отпечатка, нормализация bigint и политика неизвестного результата остаются прежними. Меняется владелец реактивного состояния и часть инфраструктуры запросов. Если при переходе исчезла проверка сети или симуляция, это не упрощение благодаря hooks, а потеря важного этапа. Полезно сопоставить каждый прежний модуль с новым ответственным компонентом.
При миграции на wagmi v3 сначала сохраняется новый профиль пакетов и перечень изменённых imports, затем по отдельности рассматриваются чтение и подключение. Автоматическая замена useAccount на useConnection без обновления зависимостей нарушает согласованность курса.