Разработка системы временного доступа через NFT и смарт-контракты

Мы регулярно видим проекты, которые пытаются натянуть временный доступ на стандартный ERC-721 и получают головную боль: race condition при проверке срока, перерасход газа из-за неоптимального хранения дат, или полное отсутствие механизма продления. Проблема в том, что ERC-721 не имеет встроенного по

Направления блокчейн-разработки

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1450
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1309
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1003
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1269
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    719
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    1009

Мы регулярно видим проекты, которые пытаются натянуть временный доступ на стандартный ERC-721 и получают головную боль: race condition при проверке срока, перерасход газа из-за неоптимального хранения дат, или полное отсутствие механизма продления. Проблема в том, что ERC-721 не имеет встроенного понятия «истечение». Решение — использовать специализированный стандарт ERC-5643 или построить свою логику на базе mapping. Мы в своей практике предпочитаем первый вариант: он проверен аудитами и сокращает время разработки на 30%. Давайте разберем, как это работает и как избежать типичных ошибок.

Как ERC-5643 решает проблему временного доступа?

В 2022 году появился ERC-5643 — расширение ERC-721 специально для подписочных NFT. Стандарт добавляет два ключевых метода:

interface IERC5643 { event SubscriptionUpdate(uint256 indexed tokenId, uint64 expiration); function renewSubscription(uint256 tokenId, uint64 duration) external payable; function cancelSubscription(uint256 tokenId) external payable; function expiresAt(uint256 tokenId) external view returns (uint64); function isRenewable(uint256 tokenId) external view returns (bool); } 

expiresAt возвращает unix timestamp истечения подписки для конкретного токена. Хранение — mapping(uint256 => uint64). uint64 достаточно для временных меток на тысячи лет вперёд и занимает один storage slot вместе с другими packed переменными.

Критическая деталь: expiresAt — это view функция, она не блокирует transfer. Если на уровне контракта нужно предотвратить передачу истёкшего токена, нужно переопределить _beforeTokenTransfer в OpenZeppelin ERC-721:

function _beforeTokenTransfer( address from, address to, uint256 tokenId, uint256 batchSize ) internal virtual override { super._beforeTokenTransfer(from, to, tokenId, batchSize); if (from != address(0) && to != address(0)) { // Блокируем transfer истёкших токенов require( block.timestamp < _expirations[tokenId], "Subscription expired" ); } } 

Альтернатива: разрешить transfer истёкших токенов, но не давать доступ. Это зависит от бизнес-модели — иногда полезно передать токен и возобновить подписку уже новому владельцу.

Off-chain проверка доступа

On-chain состояние — источник истины. Но вызывать expiresAt при каждом HTTP-запросе — медленно. Стандартная архитектура:

Backend middleware читает состояние контракта через multicall при первом обращении, кэширует результат в Redis с TTL равным сроку истечения подписки. При попытке доступа к защищённому ресурсу:

  1. Пользователь подписывает сообщение (EIP-4361 Sign-In With Ethereum)
  2. Backend верифицирует подпись, извлекает адрес кошелька
  3. Проверяет кэш Redis → если промах, запрашивает контракт
  4. Если expiresAt(tokenId) > block.timestamp — выдаёт JWT с expiry = min(subscription_expiry, JWT_max_age)

JWT инвалидируется сам по себе когда истекает. Нет необходимости держать blacklist, если JWT TTL выровнен по сроку подписки.

Продление и оплата

renewSubscription принимает duration в секундах и ETH/токен для оплаты. Важный нюанс: продление должно прибавлять к текущему сроку, а не к block.timestamp:

function renewSubscription(uint256 tokenId, uint64 duration) external payable { require(ownerOf(tokenId) == msg.sender, "Not owner"); require(msg.value >= _price * duration / 30 days, "Insufficient payment"); uint64 current = _expirations[tokenId]; // Если подписка уже истекла — продлеваем от текущего момента // Если ещё активна — добавляем к существующему сроку uint64 newExpiry = (current < uint64(block.timestamp)) ? uint64(block.timestamp) + duration : current + duration; _expirations[tokenId] = newExpiry; emit SubscriptionUpdate(tokenId, newExpiry); } 

Это принципиально для пользователя: если он продлевает активную подписку на месяц, он не теряет оставшиеся дни.

Почему Soulbound (ERC-5192) не всегда подходит?

Выбор между non-transferable (ERC-5192, Soulbound) и transferable доступом — архитектурный, не технический. Soulbound удобен для персонализированных подписок (курсы, лицензии на конкретное лицо). Transferable — для корпоративных лицензий или когда перепродажа доступа — часть модели. ERC-5192 реализуется просто: locked() возвращает true, все transfer функции revert. Однако для временного доступа часто требуется возможность продления, что проще реализовать на ERC-5643.

Стек и интеграция

Solidity 0.8.20+ с Foundry. ERC-5643 + ERC-5192 (опционально). Off-chain: Node.js/TypeScript, viem для чтения контракта, Redis для кэша доступа, JWT (jose) для сессий. Frontend: wagmi + RainbowKit для wallet connection, react-query для состояния подписки.

Для оплаты в ERC-20 (USDC/DAI) добавляется Permit2 — пользователь подписывает approval и вызов renewSubscription в одной операции, без предварительного approve.

Характеристика ERC-5643 (рекомендуется) Кастомное решение
Газовая эффективность Высокая (один storage slot) Средняя (отдельный контракт)
Аудирован Да Требуется отдельный аудит
Время разработки 2-3 дня 5-7 дней

Что входит в разработку системы временного доступа через NFT

  • Аудит требований и проектирование архитектуры (on-chain + off-chain)
  • Разработка смарт-контракта на Solidity с поддержкой ERC-5643 (или кастомной логики)
  • Разработка backend middleware для проверки подписок (Node.js + Redis)
  • Интеграция с кошельками (wagmi + RainbowKit)
  • Написание документации и тестов (unit + integration)
  • Развертывание и поддержка (1 месяц сопровождения)

Ориентиры по срокам

Базовый контракт с ERC-5643 + backend middleware проверки доступа + frontend компонент управления подпиской — 3-4 дня. С Permit2 оплатой, мультиуровневым доступом (несколько тарифов) и аналитикой продлений — 5-7 дней.

Как мы работаем

Наша команда имеет 8+ лет опыта в блокчейн-разработке, выпустила 50+ смарт-контрактов в mainnet, суммарно проаудирована на $5M+ TVL. Мы подходим к каждому проекту с учетом ваших бизнес-задач и предлагаем решение под ключ.

Свяжитесь с нами для обсуждения вашей задачи — мы предложим оптимальное решение под ваш бюджет и сроки. Пишите в Telegram или на почту, получите бесплатную оценку проекта.