Замена транзакции Ethereum и история операции
Оглавление курса «Web3-интерфейс: реестр в браузере» · Урок 20 из 30.
Отправленная транзакция может быть заменена другой транзакцией того же аккаунта с тем же nonce. Иногда пользователь повышает комиссию, иногда отменяет действие или меняет его содержание. В этом уроке сохраним историю попыток и обработаем уведомление о замене. Результат — интерфейс не теряет новую транзакцию и не считает любое изменение хеша повторной публикацией.
Предполагается журнал операции и ожидание receipt через viem 2.37.9. Замены в сети не выполнялись. Рассматриваем ожидаемую модель одного намерения издателя публиковать курс 17 и нескольких сетевых попыток его доставки. Конкретные комиссии и поведение расширения здесь не утверждаются как измеренные.
Nonce связывает конкурирующие попытки
Nonce обычного аккаунта задаёт порядок транзакций этого аккаунта в сети. Две разные транзакции с одним nonce конкурируют за одну позицию; обе не становятся последовательными успешными вызовами в одной канонической истории. Правила принятия замены зависят от узла и сетевой политики, поэтому нельзя объявить любой новый хеш заменой только по близкому времени отправки.
Увеличение комиссии может сохранить адрес, данные и сумму, меняя условия доставки. Такую попытку удобно считать ускорением того же действия. Если пользователь изменил адрес или данные, смысл операции может стать другим. Интерфейс должен сверить фактический результат и не перенести прежнее описание «публикация курса 17» на любую обнаруженную замену.
Отмена обычно выражается другой транзакцией с тем же nonce по правилам выбранного кошелька. Она тоже является сетевым действием и требует включения. Нажатие кнопки отмены в интерфейсе не стирает исходную транзакцию из всех узлов мгновенно. Пока исход не известен, наблюдение остаётся незавершённым.
Уведомление viem
Добавим onReplaced в существующее ожидание. Обработчик обновляет журнал, а итоговый receipt рассматривается отдельно. Это фрагмент модуля наблюдения, использующий прежний Public Client.
export async function observeWithReplacement(operation, save) {
const receipt = await publicClient.waitForTransactionReceipt({
hash: operation.currentHash,
timeout: 120_000,
onReplaced(info) {
operation.attempts.push({
hash: info.transaction.hash,
reason: info.reason,
replaces: info.replacedTransaction.hash,
});
operation.currentHash = info.transaction.hash;
save(operation);
},
});
return receipt;
}
В реальном модуле объект истории лучше обновлять через явный контроллер состояния или неизменяемую копию. Здесь прямое присваивание показывает связь полей без добавления ещё одного слоя абстракции. save отвечает только за запись нормализованной истории; она не должна сохранять необработанный объект библиотечного ответа с лишними полями.
Причина уведомления помогает различать переоценку комиссии, отмену и замену содержания. Но пользовательская карточка не принимает это поле как доказательство исполнения прежнего намерения. Проверяется итоговый receipt, фактическая транзакция и ожидаемые события. Старое описание операции остаётся намерением, а новые попытки получают собственные факты.
Логическая операция и хеш
Создадим локальный идентификатор операции до отправки. Он объединяет пользовательское намерение и список попыток. Хеш становится идентификатором отдельной сетевой транзакции, а не всей истории человека. Благодаря этому ссылка интерфейса на операцию остаётся стабильной после изменения хеша.
В записи находятся сеть, экземпляр, аккаунт, нормализованные аргументы и время подготовки. В каждой попытке — хеш и связь с предыдущей попыткой. Если новая транзакция не соответствует исходным аргументам, журнал показывает, что намерение изменилось или было отменено. Не нужно скрывать старый хеш: он полезен для объяснения поведения кошелька и расследования.
Ключи разных сетей не соединяются по одному nonce. Один аккаунт может иметь одинаковое значение nonce в нескольких цепочках. Поэтому сеть является обязательной частью контекста поиска замены. Для локальной лаборатории дополнительно сохраняется поколение окружения, чтобы после сброса цепочки не спутать новую историю с прежней.
Замена и текущая карточка
Если ускоренная попытка успешно вызывает publish, новое чтение показывает ожидаемый выпуск. Если вместо неё включена отмена, прежнее намерение не считается выполненным. Если заменённая операция имеет другое содержание, интерфейс объясняет отличие. Не следует трактовать успешный receipt новой транзакции как автоматический успех исходного действия.
Замена может быть инициирована вне нашей страницы — самим кошельком или другим клиентом аккаунта. Приложение не владеет всей последовательностью действий пользователя. Это причина сохранять наблюдение и понятный неизвестный статус, а не требовать, чтобы все изменения происходили через наши кнопки.
Отсутствие уведомления onReplaced не доказывает отсутствие замены. Endpoint, тайм-аут и доступная история влияют на возможность обнаружения. После восстановления страницы можно сверить известную попытку, текущий nonce и историю кошелька, однако приложение не должно придумывать точный новый хеш без данных. Неизвестность является допустимым состоянием.
Что делать пользователю
При ожидании интерфейс позволяет открыть детали операции и продолжить наблюдение. Возможность ускорения или отмены лучше делегировать кошельку, если приложение не реализует отдельный проверенный маршрут этих действий. Сайт не обещает определённого времени включения и не подставляет произвольную повышенную комиссию автоматически.
Повторное нажатие «Опубликовать» с новым nonce создаёт уже вторую логическую операцию, а не замену первой. Базовый контракт может исполнить обе. Поэтому перед повтором показывается предупреждение о незавершённой истории. Пользователь должен понимать различие между продолжением наблюдения и новым изменением реестра.
Для упражнения нарисуйте три ветви одного намерения: повышение комиссии, отмена и смена аргументов. У каждой отметьте, какой хеш становится текущим и можно ли утверждать, что выпуск опубликован. Ожидаемый итог — история не сводится к одному неизменному хешу. Поведение обработчика описано в waitForTransactionReceipt.
Замена и пользовательское описание
Текст операции не следует изменять молча с «публикация» на «отмена», сохранив прежнее время согласия. Журнал отмечает первоначальный замысел и последующее изменение сетевой попытки. Это помогает человеку понять, какое действие он подтверждал сначала и что потом сделал через кошелёк. Наличие связанного nonce не делает два разных содержания одним неизменным намерением.
Если новый хеш введён пользователем вручную, приложение сначала проверяет его форму и происхождение в выбранной сети. Оно не считает произвольную транзакцию заменой только потому, что пользователь вставил её рядом. Для подтверждённой связи нужны соответствующие данные аккаунта и nonce. Неизвестная связь показывается как дополнительная заметка, а не как установленный факт цепочки.