Розробка системи тимчасового доступу через 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 рівним сроку завершення підписки. При спробі доступу до захищеного ресурсу:
- Користувач підписує сообщення (EIP-4361 Sign-In With Ethereum)
- Backend верифікує підпис, витягує адресу гаманця
- Перевіряє кеш Redis → якщо промах, запитує контракт
- Якщо
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 днів.







