Web3-кошелёк: подтверждение и отказ от транзакции
Оглавление курса «Web3-интерфейс: реестр в браузере» · Урок 18 из 30.
После моделирования у страницы есть подготовленный запрос. Теперь пользователь должен подтвердить его в кошельке. Этот шаг добавляет возможность изменения сети и поэтому требует отдельного понятного действия. В уроке создадим Wallet Client, передадим ему подготовленный вызов и обработаем отказ. Результат — полученный хеш сохранён как начало наблюдения, а не объявлен успешной публикацией.
Предполагается объект подготовки из предыдущего урока, выбранный провайдер и текущий контекст аккаунта, сети и намерения. Профиль остаётся viem 2.37.9 и локальной цепочкой 31337. В нашей работе кошелёк не открывался и транзакции не отправлялись; код описывает маршрут будущего упражнения.
Последняя сверка контекста
Экран подтверждения показывает номер 17, новый отпечаток, адрес реестра и отправителя. Перед запросом подписи снова получаем eth_chainId и разрешённые аккаунты. Это защищает от обычной гонки между подготовкой и текущим выбором расширения. Если контекст изменился, возвращаемся к подготовке, сохраняя текст черновика.
Не нужно использовать eth_requestAccounts повторно для каждого нажатия, если разрешение уже получено. Для сверки подходит eth_accounts; пустой список означает, что текущий сайт больше не располагает подходящим аккаунтом. Новый запрос разрешения пользователь выполняет отдельно, понимая, почему он нужен.
Сверка и подпись не являются одной атомарной операцией всей браузерной среды. Пользователь всё равно может изменить расширение во время открытого окна. Поэтому кошелёк показывает фактические параметры, а приложение сохраняет контекст намерения и дальнейший сетевой результат. Успешная предварительная проверка не заменяет осознанного согласия.
Отправка через Wallet Client
Код ниже — модуль send-publication.js. Он использует local из публичной конфигурации и не получает приватный ключ. Подпись остаётся у выбранного провайдера.
import { createWalletClient, custom, getAddress } from 'viem';
import { local } from './client.js';
export async function sendPublication(provider, prepared, currentRevision) {
const expected = prepared.preparedFor;
if (currentRevision !== expected.revision) throw new Error('Подготовка устарела');
const chain = BigInt(await provider.request({ method: 'eth_chainId' }));
const accounts = await provider.request({ method: 'eth_accounts' });
if (chain !== BigInt(expected.chainId)
|| !accounts[0]
|| getAddress(accounts[0]) !== getAddress(expected.account)) {
throw new Error('Аккаунт или сеть изменились');
}
const walletClient = createWalletClient({ chain: local, transport: custom(provider) });
const hash = await walletClient.writeContract(prepared.request);
return { hash, context: expected, sentAt: new Date().toISOString() };
}
Фрагмент предполагает корректный массив адресов совместимого провайдера; на общей границе адаптера добавляются проверки формы, рассмотренные ранее. prepared.request поступает только из текущей симуляции нашего намерения. Не надо подменять адрес, аргументы или value после просмотра: изменённый запрос должен пройти новый маршрут согласия.
custom(provider) направляет запросы кошельковому источнику, а не HTTP RPC чтения. chain: local сообщает ожидаемую сеть. Это два самостоятельных клиента с разными обязанностями, хотя оба относятся к одному экземпляру приложения. Подпись не переносится на публичный сервер, и сайт не просит пользователя раскрыть seed-фразу.
Ожидание согласия
Пока окно кошелька открыто, интерфейс показывает «Ожидаем подтверждение в кошельке». Кнопка не запускает второй параллельный запрос. При отказе пользователя состояние возвращается к подготовленному намерению или к новому моделированию, если контекст успел устареть. Текст формы остаётся доступным для исправления.
Код ошибки 4001 означает пользовательский отказ в совместимом провайдере. Он не является откатом контракта и не создаёт хеш операции. Другие ошибки могут относиться к недоступности сети или неправильным параметрам. Категории следует различать, а общий текст исключения не использовать как доказательство точного состояния цепочки.
Если ответ потерян после фактической отправки, ситуация сложнее простого отказа. Приложение может не получить хеш, хотя кошелёк уже передал транзакцию. Нельзя автоматически отправлять новый вызов, чтобы «наверняка завершить». Пользователь сверяет историю кошелька, а приложение обозначает неизвестный результат доставки. В дальнейшем журнал и nonce помогут расследовать такие случаи.
Хеш начинает историю
Полученный хеш записывается сразу вместе с сетью, экземпляром, отправителем, методом и аргументами. Хранение не откладывается до закрытия вкладки. Если страница перезагрузится во время ожидания receipt, эти данные помогут продолжить наблюдение без повторной подписи.
Статус после получения хеша называется «отправлено» или «ожидает включения». Карточка курса не изменяется оптимистично как уже опубликованная. Можно показать локальное намерение отдельно, но оно не заменяет сетевой снимок. В следующем уроке receipt определит исход исполнения, затем новое чтение покажет фактическое состояние.
Хеш не является секретом, однако связь операции с пользователем и временем может быть чувствительным журналом активности. В браузерной истории сохраняется необходимый минимум, а подписи входа и личные данные туда не добавляются. Если журнал отправляется серверу, его назначение и доступ рассматриваются отдельно.
Двойное нажатие и повтор формы
Отключённая кнопка уменьшает вероятность двойного запроса, но программный обработчик тоже должен иметь флаг активной отправки. DOM можно изменить, а быстрые события могут попасть в очередь до перерисовки. Ответственность за один пользовательский маршрут лучше выразить в контроллере, а не только визуальным состоянием.
При этом базовый контракт допускает несколько публикаций одинакового отпечатка. Даже две успешные операции не создадут два курса в mapping, но создадут два события и потребуют комиссии. Интерфейс объясняет такой повтор, а не считает идентичные аргументы автоматической гарантией отсутствия второго сетевого действия.
Для упражнения рассмотрите согласие, отказ, смену аккаунта до отправки и потерю ответа после отправки. Запишите, когда существует хеш, а когда результат остаётся неизвестным. Ожидаемый итог — подпись становится явным этапом, который не путается ни с подготовкой, ни с исполнением. API описан в Wallet Client и writeContract.
Время ожидания кошелька
Пользователь может долго читать параметры в расширении. Не стоит автоматически признавать такое ожидание отказом и сразу открывать второе окно. Контроллер сохраняет один активный маршрут и позволяет человеку вернуться к странице. Если контекст приложения за это время изменился, результат связывается с исходным намерением и отдельно требует сверки.
Сайт не может гарантировать качество отображения параметров любым кошельком. Поэтому перед запросом он сам показывает понятное действие и экземпляр. Подтверждение расширения является последним моментом согласия пользователя, а не заменой предварительного просмотра страницы. Чем меньше неожиданно изменённых параметров между этими экранами, тем понятнее маршрут.
После закрытия экрана не следует стирать отправку, которая ещё ожидала ответа провайдера. Контроллер отмечает, что результат может прийти позже, и обрабатывает его в журнале по известному контексту, не перерисовывая удалённый DOM.