Разработка системы session keys для dApp
Представьте GameFi-протокол: пользователь покупает предмет, делает ход, крафтит оружие — каждое действие требует подписи в MetaMask. За час игры — 30-50 всплывающих окон. Пользователь уходит после пятого. Session keys решают это: пользователь подписывает один раз, выдавая ограниченные права временному ключу, который подписывает остальные транзакции автоматически.
Как работают session keys
Базовая концепция — делегирование подписей с ограничениями. Основной аккаунт (EOA или смарт-кошелёк) выдаёт сессионный ключ права на выполнение конкретных операций в течение определённого времени. Сессионный ключ хранится в памяти браузера или в backend-сервисе, подписывает транзакции без участия пользователя, и автоматически теряет силу по истечению сессии.
Это работает только со смарт-кошельками (Account Abstraction) — EOA не умеет делегировать подписи с ограничениями. Поэтому session keys неразрывно связаны с EIP-4337.
Архитектура в контексте EIP-4337
В стандартном EIP-4337 флоу: смарт-кошелёк (Account) получает UserOperation, его validateUserOp проверяет подпись. Для session keys логика расширяется:
-
validateUserOpпроверяет, является ли подпись сессионным ключом - Если да — проверяет, зарегистрирован ли ключ в хранилище сессий контракта
- Проверяет, не истёк ли срок действия (
validUntiltimestamp) - Проверяет, допустима ли вызываемая функция (whitelist по
selector) - Проверяет, не превышен ли лимит на операцию или суммарный лимит за сессию
Этот паттерн реализован в Kernel (ZeroDev), Biconomy Smart Account v2, и Rhinestone Module SDK. Каждая реализация немного отличается структурой permission-объекта, но логика одна.
Типичные грабли при реализации
Scope слишком широкий
Самая частая ошибка при выдаче session key — слишком широкие права. «Может вызывать любую функцию контракта X» вместо «может вызывать только buyItem(uint256) с amount ≤ 10 USDC». Широкий scope нивелирует смысл session keys: компрометированный сессионный ключ даёт атакующему полный доступ к аккаунту в контексте этого контракта.
Правильная структура permissions включает:
-
target— адрес контракта, с которым разрешено взаимодействие -
selector— конкретный function selector (4 байта) -
valueLimit— максимальный ETH в одной транзакции -
callCountLimit— максимальное количество вызовов за сессию -
validAfter/validUntil— временные границы
Хранение ключа на клиенте
Сессионный ключ — это приватный ключ. Хранить его в localStorage напрямую — опасно, XSS-атака сольёт ключ. Правильный подход: sessionStorage (очищается при закрытии вкладки) или шифрование через WebCrypto API с ключом, привязанным к user-specific данным.
Для более серьёзных систем — сессионный ключ генерируется в WebWorker с изолированным контекстом, или выносится на backend с выдачей подписи через API (тогда backend никогда не видит полный ключ основного аккаунта).
Отсутствие механизма отзыва
Сессия должна отзываться в трёх сценариях: пользователь нажал «выйти», сессия истекла, обнаружена подозрительная активность. Отзыв реализуется через revokeSession(bytes32 sessionKeyHash) в контракте кошелька — функция помечает ключ как недействительный в on-chain mapping. После этого все транзакции с этим ключом будут реверсированы в validateUserOp.
Без механизма отзыва утечка сессионного ключа = продолжение атаки до истечения времени сессии.
Стек реализации
ZeroDev Kernel — наиболее зрелая реализация с plugin-архитектурой. Session key как plugin устанавливается через kernel.execute(installPlugin(sessionKeyPlugin)). Поддерживает ERC-7579 (модульные smart accounts), что позволяет переиспользовать плагин между разными реализациями кошельков.
Rhinestone Module SDK — модульный подход, где каждый permission-тип — отдельный валидатор. Гибко, но требует понимания ERC-7579 интерфейсов.
permissionless.js от Pimlico — клиентская библиотека для работы с EIP-4337, включая session keys. Интегрируется с viem и wagmi.
Bundler для UserOperation submission: Alto (Pimlico) или Stackup — оба поддерживают Polygon, Ethereum, Arbitrum, Optimism.
Процесс разработки
Проектирование permission schema (1-2 дня). Определяем, какие операции разрешены в сессии, какие лимиты, как хранится ключ на клиенте.
Смарт-контракт: session key validator (3-5 дней). Кастомный validator для Kernel или использование готового с настройкой permissions. Тесты в Foundry с эмуляцией UserOperation flow.
Клиентская часть (3-5 дней). Генерация сессионного ключа, его выдача через подпись основного аккаунта, хранение в sessionStorage, подписание UserOperation сессионным ключом, отправка через bundler API.
Управление жизненным циклом (1-2 дня). UI для просмотра активных сессий, отзыва конкретной сессии, авто-отзыва при logout.
Ориентиры по срокам
Базовая система с одним типом permissions на существующем смарт-кошельке — 1-2 недели. Кастомная реализация со сложной permission-матрицей и нестандартными ограничениями — 3-5 недель. Интеграция с уже работающим dApp, которое не использует Account Abstraction — требует предварительной миграции на AA, что удваивает сроки.
Стоимость рассчитывается после аудита текущей архитектуры кошелькового взаимодействия.







