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

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка системи тимчасового доступу через NFT
Середній
~3-5 днів
Часті запитання

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

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1285
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1121
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    858

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

Більшість проектів, які хочуть реалізувати тимчасовий доступ через NFT, наштовхуються на одну й ту ж проблему: ERC-721 спочатку не містить поняття «срок дії». Токен либо є в гаманці, либо ні. Дата закінчення — це додаткова логіка, яку потрібно правильно вбудовувати, інакше вийде або газове болото, або race condition між перевіркою та виконанням.

Архітектура тимчасового доступу

ERC-5643: стандарт підписок

У 2022 році з'явився ERC-5643 — розширення ERC-721 спеціально для subscription 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 vs. transferable

Вибір між non-transferable (ERC-5192, Soulbound) та transferable доступом — архітектурний, не технічний. Soulbound зручний для персоналізованих підписок (курси, ліцензії на конкретну особу). Transferable — для корпоративних ліцензій або коли перепродаж доступу — частина моделі.

ERC-5192 реалізується просто: locked() повертає true, всі transfer функції revert. OpenZeppelin 5.x додав ERC721Votes та ERC721Enumerable як extensions — Soulbound реалізується аналогічно через _update hook.

Stack та інтеграція

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

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

Орієнтири по строкам

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