Перейти к содержанию

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-вызова с указанным отправителем; серверный вход требует отдельного доказательства и одноразового сообщения.

Оглавление серии