Разработка системы meta-транзакций (EIP-2771)

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы meta-транзакций (EIP-2771)
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка системы meta-транзакций (EIP-2771)

Пользователь установил приложение, получил NFT или токены, хочет что-то сделать — и натыкается на «нужно ETH для газа». На этом шаге теряется от 30 до 60% новых пользователей в зависимости от аудитории. Meta-транзакции — механизм, который убирает это препятствие: пользователь подписывает намерение, приложение оплачивает газ.

EIP-2771 стандартизировал архитектуру: trusted forwarder — контракт, которому целевой контракт доверяет пересылать вызовы с сохранением оригинального msg.sender.

Как работает EIP-2771

Без meta-транзакций: user → (напрямую) → Contract. msg.sender в контракте — это адрес пользователя.

С meta-транзакциями: user → (подписанный запрос) → Relayer → Forwarder → Contract. msg.sender в контракте — это адрес Forwarder. Контракт не знает настоящего отправителя.

Решение — контракт проверяет, что msg.sender является доверенным forwarder'ом, и тогда читает настоящий адрес из последних 20 байт calldata:

// OpenZeppelin ERC2771Context
function _msgSender() internal view virtual override returns (address) {
    if (isTrustedForwarder(msg.sender) && msg.data.length >= 20) {
        return address(bytes20(msg.data[msg.data.length - 20:]));
    }
    return super._msgSender();
}

Все msg.sender в бизнес-логике контракта нужно заменить на _msgSender(). Это единственное изменение в существующем контракте — если он наследует ERC2771Context от OpenZeppelin.

Компоненты системы

Trusted Forwarder

Валидирует подписи пользователей (EIP-712 typed data), проверяет nonce (защита от replay), пересылает вызов в целевой контракт, добавляя адрес пользователя в конец calldata.

OpenZeppelin MinimalForwarder — простая реализация, подходит для начала. Для production рекомендуем OpenGSN Forwarder или собственный с дополнительными проверками: deadline, domain separator, address whitelisting.

struct ForwardRequest {
    address from;      // пользователь
    address to;        // целевой контракт
    uint256 value;     // ETH (обычно 0)
    uint256 gas;       // лимит газа
    uint256 nonce;     // защита от replay
    bytes data;        // calldata
}

Подпись EIP-712

Пользователь подписывает структурированные данные, а не сырой хэш. Это позволяет MetaMask и другим кошелькам показывать человекочитаемое содержимое запроса перед подписанием.

// Клиент: подготовка подписи
const domain = {
    name: "MyForwarder",
    version: "1",
    chainId: await signer.getChainId(),
    verifyingContract: forwarderAddress,
};

const signature = await signer.signTypedData(domain, types, request);

Relayer

Принимает подписанный запрос, проверяет его валидность, отправляет транзакцию за пользователя, оплачивая газ. Может быть:

  • Централизованным — собственный backend, самый простой вариант
  • OpenGSN — децентрализованная сеть relayer'ов
  • Biconomy — managed сервис с dashboard и analytics
  • Gelato Network — для автоматизации и условных вызовов

Для большинства проектов на старте — централизованный relayer на собственном backend. Это проще, быстрее и дешевле, пока TPS небольшой. Децентрализация нужна, когда централизованный relayer становится точкой отказа с реальными последствиями.

Ключевые уязвимости и защиты

Replay attack. Подписанный запрос без nonce или с предсказуемым nonce может быть исполнен несколько раз. Forwarder должен хранить nonce per-user и инкрементировать после каждого успешного вызова.

Gas griefing. Пользователь указывает минимальный gas в запросе, relayer отправляет транзакцию с этим лимитом — контракт падает с out-of-gas, но газ потрачен. Решение: relayer проверяет, что у него достаточно газа для выполнения + overhead на forwarder logic.

Forwarder spoofing. Если контракт принимает любой forwarder как доверенный — атакующий может подделать msg.sender. Список доверенных forwarder'ов должен быть фиксированным или изменяемым только через multisig.

_msgSender() vs msg.sender. Самая распространённая ошибка при интеграции EIP-2771 — использование msg.sender там, где должен быть _msgSender(). Статический анализ через Slither ловит часть таких случаев, но не все.

Интеграция с существующим контрактом

Если контракт уже в production без поддержки EIP-2771 — его нельзя изменить (без upgrade proxy). Есть обходной путь: meta-транзакции через EIP-1271 (contract signatures), где пользователь деплоит собственный аккаунт-контракт. Но это сложнее и дороже для пользователя.

Вывод: если meta-транзакции нужны, закладывать поддержку ERC2771Context нужно на этапе первоначальной разработки, не после.

Процесс и сроки

Компонент Срок
Интеграция ERC2771Context в контракт + тесты 1 день
MinimalForwarder деплой + конфигурация 0.5 дня
Backend relayer (Node.js + ethers.js) 1-2 дня
Frontend интеграция (wagmi + signTypedData) 1 день
Тесты end-to-end сценариев 1 день

Итого для полной системы с централизованным relayer: 3-5 рабочих дней. С Biconomy или OpenGSN вместо собственного backend: 2-3 дня (меньше backend-работы, больше конфигурации). Стоимость зависит от состояния существующего контракта и выбора инфраструктуры relayer.