Viem: симуляция вызова перед отправкой транзакции
Оглавление курса «Web3-интерфейс: реестр в браузере» · Урок 17 из 30.
Перед запросом подписи полезно выяснить, может ли выбранный вызов исполниться при наблюдаемом состоянии. Viem предоставляет simulateContract, который использует публичный клиент и возвращает подготовленный запрос для записи. В этом уроке соединим намерение издателя с симуляцией. Результат — интерфейс различает корректно подготовленный вызов и откат, не меняя контракт.
Предполагается проверенное намерение publish(17, contentHash) из предыдущего урока. Сеть остаётся локальной 31337, библиотека — viem 2.37.9. Реальное моделирование, газовые измерения и отправка не выполнялись. Объясняем ожидаемое поведение API по первичной документации и исходной модели контракта.
Что делает моделирование
Симуляция исполняет вызов в контексте чтения узла и сообщает результат или причину отката. Она не публикует транзакцию и не изменяет постоянное состояние сети. Пользователь не платит сетевую комиссию за такой клиентский запрос, но RPC всё равно выполняет вычисления и может ограничивать нагрузку.
Важен параметр account. Базовый контракт проверяет msg.sender, поэтому симуляция без явного отправителя не описывает намерение конкретного издателя. Нужно использовать адрес, выбранный в актуальном кошельке, а не publisher из реестра как подставного отправителя ради успешной проверки. Иначе интерфейс покажет успех для человека, который не может подписать соответствующую операцию.
Симуляция относится к определённому наблюдаемому состоянию. Между ней и включением транзакции могут измениться данные или условия исполнения. Поэтому успешное предварительное моделирование не является обещанием будущего успеха и не позволяет скрыть дальнейшие статусы. Оно сокращает число очевидно неправильных запросов до подписи.
Подготовка запроса
Создадим модуль simulate-publication.js. Он получает уже нормализованное намерение и возвращает запрос вместе со снимком контекста, для которого тот был подготовлен.
import { publicClient } from './client.js';
import { catalogAbi } from './catalog-abi.js';
export async function simulatePublication(intent) {
const blockNumber = await publicClient.getBlockNumber();
const { request, result } = await publicClient.simulateContract({
address: intent.address,
abi: catalogAbi,
functionName: 'publish',
args: intent.args,
account: intent.account,
blockNumber,
});
return {
request,
result,
preparedFor: {
account: intent.account,
chainId: intent.chainId,
address: intent.address,
revision: intent.revision,
blockNumber,
},
};
}
Метод publish не возвращает полезного значения, поэтому result не становится новым выпуском. Главный выход — отсутствие отката и request, который далее передаётся совместимому Wallet Client. Карточка текущего курса при этом остаётся прежней: симуляция не записала новый отпечаток.
Параметр блока делает происхождение проверки явным. Он не удерживает этот блок текущим до подписи. Перед отправкой приложение проверяет поколение контекста и при необходимости готовит запрос снова. Если правило продукта требует свежего наблюдения, срок пригодности подготовки задаётся отдельно, а не выводится из самого наличия объекта request.
Причина отката
Если отправитель не издатель, ожидается Unauthorized. Нулевой номер или отпечаток приводит к InvalidCourse, однако проверка отправителя в модификаторе выполняется раньше тела. Поэтому читатель с неверными аргументами может получить прежде всего ошибку полномочий. Порядок объясняется исходником, а не случайным выбором текста клиента.
ABI содержит определения собственных ошибок, чтобы библиотека могла их разобрать. Если endpoint не вернул данные отката, клиент может сообщить более общую ошибку. Нельзя в таком случае объявлять точное имя ошибки по догадке. Интерфейс показывает доступную категорию, а технические подробности оставляет в диагностической области.
Сетевой тайм-аут не является откатом publish. Если ответа симуляции нет, результат проверки неизвестен и кнопка подписи не становится готовой автоматически. Можно предложить повторить подготовку, сохранив введённые аргументы. Повторение этой операции не отправляет транзакцию, но всё равно должно быть ограниченным и понятным человеку.
Проверка намерения после ответа
Во время симуляции пользователь мог изменить аккаунт. Возвращённый запрос всё ещё описывает старого отправителя. Применяем его только тогда, когда preparedFor.revision совпадает с текущим поколением, а сеть, экземпляр и аккаунт остаются теми же. Самый простой интерфейс при несовпадении сообщает «Контекст изменился, подготовьте действие заново».
Изменение текста формы тоже увеличивает поколение намерения. Например, пользователь заменил отпечаток A на B, пока моделировался A. Нельзя разрешить подпись результата A под видимым текстом B. В дополнение к контексту кошелька сохраняют идентификатор или отпечаток нормализованного намерения, который связывает запрос с конкретными аргументами.
Не нужно копировать произвольные поля подготовленного запроса в форму и позволять их незаметно менять после просмотра. Изменение адреса назначения, суммы ETH или метода требует нового подтверждения и новой проверки. Передача request должна оставаться частью явного маршрута данных, а не скрытой магией общего объекта состояния.
Газ и ожидание
Симуляция не является точной оценкой всех расходов. Для оценки исполнения может использоваться отдельный estimateContractGas, а итоговая комиссия зависит также от цены газа и сетевых правил. Даже такая оценка описывает условия момента запроса и может измениться. Не следует выводить в форме заранее выдуманное постоянное число стоимости публикации.
В нашем базовом контракте value не нужен: публикация неплатёжная. Добавление суммы ETH к вызову nonpayable приведёт к несовместимому поведению. Если позже появится платная функция, её ABI, условия и интерфейс вводятся отдельно. Комиссия сети и передаваемая контракту сумма имеют разные назначения.
Для упражнения сравните симуляцию от издателя и от читателя, затем измените отпечаток формы до завершения ответа. Ожидается отказ читателя и признание старого результата устаревшим. Все эти ожидания являются критериями будущего самостоятельного запуска, а не отчётом о проведённой проверке. Маршрут подготовки описан в simulateContract.
Отдельный результат для интерфейса
Успешное моделирование удобно отображать текстом «Вызов подготовлен без обнаруженного отката». Формулировка точнее обещания «транзакция точно пройдёт». Пользователь понимает, что проверена текущая модель, а согласие и включение ещё впереди. При изменении блока или контекста статус может потребовать обновления, не становясь необъяснимой ошибкой сайта.
Для журналирования сохраняют номер блока, аккаунт и идентификатор намерения, а не полный непрозрачный объект библиотечной ошибки или внутреннего клиента. Такой небольшой документ позволяет объяснить происхождение подготовки и восстановить сценарий в будущем. Он не является подписанной транзакцией и не должен попадать в очередь автоматического исполнения.
Подготовленная симуляция не удостоверяет личность издателя для сервера. Она сообщает результат EVM-вызова с указанным отправителем; серверный вход требует отдельного доказательства и одноразового сообщения.