Ошибки смарт-контракта в Web3-интерфейсе
Оглавление курса «Web3-интерфейс: реестр в браузере» · Урок 22 из 30.
Техническая ошибка становится полезной, когда интерфейс объясняет её в контексте действия. Unauthorized, пользовательский отказ и недоступный RPC требуют разных решений. В этом уроке выделим безопасную классификацию ошибок и не будем выводить исключение целиком как пользовательское сообщение. Результат — форма курса 17 помогает исправить причину, сохраняя черновик и факты уже отправленной операции.
Предполагаются ABI с собственными ошибками и маршрут симуляции, подписи и receipt. Используем viem 2.37.9. Примеры не исполнялись; категории описывают ожидаемую обработку, а не список фактически обнаруженных дефектов проекта.
Граница, на которой возникла ошибка
Одинаковая строка «не удалось выполнить» может относиться к подготовке аргументов, чтению сети, согласию кошелька или исполнению включённой транзакции. Поэтому вместе с исключением контроллер знает этап. Если ошибка возникла до отправки, хеша ещё нет. Если после хеша, история операции сохраняется и результат может быть неизвестен. Этот контекст важнее случайного текста исключения.
Базовый CourseCatalog имеет три собственные ошибки. Unauthorized сообщает об отправителе, который не равен издателю. InvalidCourse относится к нулевому номеру или отпечатку. NotActive возникает при попытке деактивировать уже неактивную запись. Ни одна из них не является общим обозначением отсутствия файла манифеста или неработающего сайта.
Ошибки из ABI полезны при симуляции. Receipt откатившейся транзакции обычно не содержит полный текст причины в таком же виде. Для расследования могут понадобиться отдельные данные и возможности узла. Интерфейс не должен выдумывать точную причину только по статусу reverted, даже если похожая симуляция раньше показала конкретное имя.
Разбор библиотечного исключения
Viem оборачивает причины в структуру ошибок. Используем поддерживаемый поиск вложенной ошибки вместо проверки произвольного текста сообщения. Это фрагмент classify-error.js.
import { BaseError, ContractFunctionRevertedError } from 'viem';
const messages = {
Unauthorized: 'Этот аккаунт не является издателем выбранного реестра.',
InvalidCourse: 'Номер курса или отпечаток не подходит для публикации.',
NotActive: 'У курса уже нет активной записи для деактивации.',
};
export function classifyError(error, stage) {
if (error instanceof BaseError) {
const reverted = error.walk(cause => cause instanceof ContractFunctionRevertedError);
if (reverted instanceof ContractFunctionRevertedError) {
const name = reverted.data?.errorName;
return { category: 'contract', stage, message: messages[name] ?? 'Контракт отклонил вызов.' };
}
}
if (error?.code === 4001) {
return { category: 'rejected', stage, message: 'Действие отменено в кошельке.' };
}
return { category: 'unknown', stage, message: 'Не удалось завершить этот этап. Посмотрите детали операции.' };
}
Это не исчерпывающий классификатор всех библиотечных и провайдерских ошибок. Отказ может оказаться внутри другой обёртки, а ошибки транспорта имеют дополнительные типы. Реальный адаптер расширяется по документированным структурам, сохраняя общую безопасную категорию для неизвестного случая. Лучше признать неопределённость, чем предложить неверное действие по совпадению нескольких слов.
Нельзя выделять причину через поиск Unauthorized в произвольном message. Текст может измениться между версиями, содержать описание другого контракта или быть сформирован недоверенным источником. Декодированное имя из подходящего ABI имеет более точное происхождение, хотя тоже относится только к выбранному экземпляру и наблюдению.
Сообщение и технические детали
Первая строка объясняет человеку, что сделать: выбрать издателя, исправить аргументы, обновить снимок или продолжить наблюдение. Техническая область может показать метод, сеть, экземпляр и безопасную категорию. Полные URL endpoint с секретными параметрами, подписи входа и личные данные туда не добавляются.
Вывод сообщения выполняется через textContent. Исключение не вставляется как HTML, даже если пришло от знакомой библиотеки. RPC и кошелёк могут передавать данные стороннего происхождения. Нормальная защита веб-интерфейса не исчезает из-за того, что ошибка относится к EVM.
Если номер или отпечаток неправильный, фокус можно вернуть к соответствующему полю, а описание ошибки связать с ним через aria-describedby. При сетевом отказе фокус не прыгает в случайное поле: там нет ошибки ввода. Удобство и доступность требуют такой же классификации причины, как логика контроллера.
Что сохранять после ошибки
Неверный аргумент не уничтожает весь черновик. Пользователь исправляет конкретное значение. Отказ подписи сохраняет намерение, но подготовку может потребоваться обновить, если прошло время или сменился контекст. Сетевой тайм-аут после хеша сохраняет журнал операции и предлагает чтение результата, а не повторную подпись.
Откат deactivate из-за NotActive служит поводом обновить карточку. Возможно, другой вызов уже изменил состояние. Базовый контракт не хранит человекочитаемое объяснение деактивации, поэтому интерфейс не придумывает его из ошибки. История событий показывает факт, а редакционная причина может храниться во внешнем журнале.
Если ошибка относится к несовместимому ABI или адресу, обычный пользователь не обязан выбирать другой аккаунт. Проблема конфигурации требует исправления приложения. В интерфейсе полезно назвать невозможность прочитать выбранный реестр и предоставить безопасные сведения для обращения к сопровождающему проект человеку.
Повтор по категории
Повтор подготовки допустим после исправления аргументов или обновления контекста. Повтор чтения допустим после временного транспортного сбоя. Автоматический повтор отправки требует отдельного решения и обычно не является правильной реакцией на неизвестность. Один общий обработчик «при ошибке попробуй ещё раз» опасен именно смешением этих этапов.
Для упражнения распределите пять случаев: неверный отпечаток, отказ кошелька, Unauthorized, тайм-аут receipt и ошибка рендеринга после успеха. Запишите, какие данные уже существуют и какое действие можно повторить без изменения сети. Ожидаемый итог — пользователь получает конкретную помощь, а приложение не маскирует неизвестный результат. Работа ошибок описана в документации viem.
Неизвестная собственная ошибка
После обновления контракта может появиться ошибка, которой нет в текущем ABI. В таком случае правильнее показать общий откат и безопасные технические данные, чем подставить ближайшее знакомое название. Эта ситуация сигнализирует о проверке совместимости экземпляра и приложения. Поддержка новой ошибки вводится вместе с новой версией ABI, а не по случайной строке из endpoint.
Пользовательский текст не должен обещать, что простое повторение исправит неизвестность. Если причина не определена, предложите посмотреть контекст и сохранить данные для разбора. Команда поддержки получает метод, сеть, экземпляр и этап, но не приватные сведения. Такая диагностическая запись помогает воспроизвести проблему в будущем без необоснованного доступа к кошельку человека.
При ошибке уже включённой операции сохраняется receipt, даже если приложение не умеет декодировать причину. Факт отката известен, точная причина может быть неизвестна. Это лучше, чем заменить всё одним полем error = true и потерять полезные сведения для восстановления.
Не переводите неизвестную причину в знакомую ошибку только ради красивого текста. Явное ограничение диагностики позволяет человеку сохранить контекст и избежать ошибочного повторного действия.