Paymaster в ERC-4337: оплата операции аккаунта
Оглавление курса «Web3: вход, смарт-аккаунты и индексатор» · Урок 24 из 30.
Мы уже различаем намерение, его проверку и доставку. Теперь добавим участника, который может оплатить выполнение вместо аккаунта пользователя. В ERC-4337 эту роль называют paymaster. Для учебной библиотеки рассмотрим ограниченный бюджет на публикации курса 17: администрация готова оплачивать определённые действия преподавателя, но не любые вызовы и не неограниченное число повторов.
Главный результат урока — собственная модель заявки на оплату и её жизненного цикла. Это описание политики приложения, которое не запускалось и не является реализацией paymaster-контракта. Здесь нет действительных депозитов, тарифов провайдера или обещания бесплатных операций. Кто-то всё равно несёт затраты, даже когда интерфейс не предлагает пользователю оплачивать их самостоятельно.
Оплата не расширяет полномочия
У библиотеки существуют независимые вопросы. Имеет ли смарт-аккаунт право изменить реестр? Разрешена ли конкретная операция политикой аккаунта? Готов ли спонсор оплачивать её доставку? Положительный ответ на третий вопрос не заменяет первые два. Paymaster не превращает обычного читателя в publisher и не исправляет вызов неправильного контракта.
Стандарт описывает paymaster как дополнительного участника оплаты обработки операции. Конкретные правила допуска, проверок и последующего учёта относятся к выбранной реализации. Механизм paymaster в ERC-4337. Для нашей лаборатории эти правила формулируются отдельно от сетевого формата, чтобы не выдавать бизнес-JSON за контрактные байты.
Представим попытку опубликовать курс 18. Авторизация владельца аккаунта может её разрешать, но политика библиотеки оплачивает только курс 17. Получается понятный отказ спонсора, а не ошибка подписи. Пользователь может выбрать другой законный путь оплаты, если он существует в профиле, однако приложение не должно тайно расширять спонсируемую область.
Структура разрешения на оплату
Наша заявка связывает бюджет с одним подготовленным намерением. Она включает контекст, срок и верхнюю границу затрат, выбранную самим спонсором. Числа ниже относятся к искусственным единицам учёта лаборатории. Их нельзя переносить в gas limits, wei или тариф конкретной сети.
export function sponsorshipDecision(intent, policy, now) {
if (!policy.enabled) return { ok: false, reason: 'disabled' };
if (now >= policy.expiresAt) return { ok: false, reason: 'expired' };
if (intent.environmentId !== policy.environmentId) {
return { ok: false, reason: 'environment' };
}
if (intent.sender !== policy.allowedSender) return { ok: false, reason: 'sender' };
if (intent.target !== policy.catalogAddress) return { ok: false, reason: 'target' };
if (intent.action !== 'publish' || intent.courseId !== 17n || intent.value !== 0n) {
return { ok: false, reason: 'scope' };
}
if (policy.remainingUnits < policy.reservationUnits) {
return { ok: false, reason: 'budget' };
}
return { ok: true, reservedUnits: policy.reservationUnits };
}
Даже правильное решение функции не является подписанным сетевым разрешением. Оно только говорит, что наша политика согласилась зарезервировать ресурс. Реальный адаптер должен связать разрешение с окончательной операцией и защитить его от изменения полей, повторного использования и переноса между сетями. Способ этой связи зависит от версии реализации.
Нельзя сначала одобрить intent, а затем добавить в окончательный пакет второй вызов. Пользовательская форма и платёжная политика должны описывать один и тот же набор действий. В универсальном аккаунте необходимо учитывать batch, вложенные execute и изменение целевого маршрута. В учебной модели пакетных действий нет, что позволяет оставить область допуска строго ограниченной.
Резервирование и учёт результата
Если два параллельных запроса читают одинаковый остаток, обе попытки могут получить разрешение и превысить бюджет. Поэтому проверка остатка и резервирование должны быть атомарным действием хранилища. Обычный JavaScript-объект подходит для разборов одного процесса, но не решает конкуренцию нескольких серверов.
Состояния нашей заявки выглядят так: доступный бюджет, резерв, операция доставляется, известен расход, остаток пересчитан. Сбой транспорта оставляет резерв в неопределённом состоянии до выяснения судьбы операции. Нельзя немедленно вернуть всё в свободный бюджет только потому, что браузер потерял ответ: операция могла уже попасть в обработку.
export function settleReservation(reservation, observation) {
if (!observation.authoritative) {
return { ...reservation, state: 'pending', releaseUnits: 0 };
}
if (observation.chargedUnits > reservation.reservedUnits) {
throw new Error('Нарушена граница учебного бюджета');
}
return {
...reservation,
state: 'settled',
chargedUnits: observation.chargedUnits,
releaseUnits: reservation.reservedUnits - observation.chargedUnits,
};
}
Поле authoritative обозначает решение доверенного адаптера учёта, а не булево значение из запроса посетителя. Реальный источник расхода, комиссии и резервов фиксируется в договоре сервиса. Модель лишь показывает правило: возвращать остаток разрешено после достаточного наблюдения. Истечение внутреннего таймера само по себе не создаёт такого доказательства.
Ошибки и понятный интерфейс
Отказ оплаты показываем отдельно от ошибки выполнения. Например, «бюджет спонсора исчерпан» не означает, что publish незаконен. «Реестр отклонил действие» не означает, что газ никем не оплачен. Неуспешное выполнение может всё равно потреблять ресурсы, поэтому понятие оплаченного результата не совпадает с понятием успешного изменения курса.
Не предлагаемся скрытую автоматическую замену спонсора на личную оплату пользователя. Если доступен альтернативный путь, показываем его заранее и получаем отдельное согласие на соответствующую операцию. Пользователь должен понимать, чей бюджет используется и какую максимальную ответственность он принимает. Это требование особенно полезно при повторной отправке после неизвестного результата.
Для локальной модели достаточно трёх карточек: разрешено в пределах политики, отказ с причиной и ожидается окончательный учёт. В общедоступную статистику не выводим полный список личных подписей, внутренние ключи сервиса или секреты его API. Прозрачность расхода строится на безопасных агрегатах и идентификаторах операций.
Ожидаемые случаи
В первом случае преподаватель публикует курс 17 в согласованном окружении. Политика создаёт резерв, доставка начинается, затем наблюдение сообщает расход меньше резерва. Часть возвращается в доступный остаток. Хеш курса меняется только после успешного исполнения, а не после самого резервирования.
Во втором случае та же заявка просит передать value больше нуля. Политика отклоняет её до выделения бюджета, потому что базовый publish не требует перевода. В третьем случае ответ потерян. Резерв остаётся pending, а интерфейс предлагает узнать результат исходной операции, сохраняя запрет на слепой повтор.
После урока оплата получила собственную границу доверия и собственный учёт. Дальше рассмотрим nonce: даже разрешённые и оплаченные намерения должны быть защищены от повторов и правильно упорядочены внутри смарт-аккаунта.
Справедливое расходование общего бюджета
Даже узкая политика курса 17 может быть исчерпана частыми законными публикациями. Поэтому бюджет дополняют ограничением скорости и понятным правилом допуска участников. Эти условия не следует скрывать от преподавателя: отказ по квоте объясняется отдельно от нехватки общего ресурса.
Для модели можно назначить каждой заявке устойчивый идентификатор и связать резерв с ним. Повтор чтения статуса не создаёт новый резерв. Если два сервера одновременно обрабатывают один идентификатор, хранилище обязано сохранить единственную запись. Это свойство приложения, которое не получается автоматически из наличия paymaster в сетевом профиле.