NFT-Based Temporary Access System Development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
NFT-Based Temporary Access System Development
Medium
~3-5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

NFT Temporary Access System Development

Most projects that want to implement temporary access via NFT run into the same problem: ERC-721 originally has no concept of "expiration." A token either exists in your wallet or it doesn't. Expiration date is additional logic that must be embedded correctly, otherwise you get either a gas swamp or a race condition between checking and execution.

Temporary Access Architecture

ERC-5643: Subscription Standard

In 2022, ERC-5643 emerged — an extension of ERC-721 specifically for subscription NFTs. The standard adds two key methods:

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 returns the unix timestamp when a subscription for a specific token expires. Storage: mapping(uint256 => uint64). uint64 is sufficient for timestamps thousands of years into the future and fits in one storage slot with other packed variables.

Critical detail: expiresAt is a view function, it doesn't block transfers. If you need to prevent transfer of expired tokens at the contract level, override _beforeTokenTransfer in 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)) {
        // Block transfer of expired tokens
        require(
            block.timestamp < _expirations[tokenId],
            "Subscription expired"
        );
    }
}

Alternative: allow transfer of expired tokens but deny access. This depends on business model — sometimes useful to transfer the token and renew subscription under new owner.

Off-chain Access Verification

On-chain state is source of truth. But calling expiresAt on every HTTP request is slow. Standard architecture:

Backend middleware reads contract state via multicall on first access, caches result in Redis with TTL equal to subscription expiry. On attempt to access protected resource:

  1. User signs message (EIP-4361 Sign-In With Ethereum)
  2. Backend verifies signature, extracts wallet address
  3. Checks Redis cache → if miss, queries contract
  4. If expiresAt(tokenId) > block.timestamp — issue JWT with expiry = min(subscription_expiry, JWT_max_age)

JWT invalidates itself when it expires. No need for blacklist if JWT TTL is aligned with subscription expiry.

Renewal and Payment

renewSubscription accepts duration in seconds and ETH/token for payment. Important nuance: renewal should add to current expiry, not 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];
    // If subscription expired — renew from current moment
    // If still active — add to existing expiry
    uint64 newExpiry = (current < uint64(block.timestamp))
        ? uint64(block.timestamp) + duration
        : current + duration;

    _expirations[tokenId] = newExpiry;
    emit SubscriptionUpdate(tokenId, newExpiry);
}

This is critical for user: if they renew active subscription for a month, they don't lose remaining days.

Soulbound vs. Transferable

Choice between non-transferable (ERC-5192, Soulbound) and transferable access is architectural, not technical. Soulbound convenient for personalized subscriptions (courses, licenses for specific person). Transferable — for corporate licenses or when resale of access is part of model.

ERC-5192 implemented simply: locked() returns true, all transfer functions revert. OpenZeppelin 5.x added ERC721Votes and ERC721Enumerable as extensions — Soulbound implemented similarly via _update hook.

Stack and Integration

Solidity 0.8.20+ with Foundry. ERC-5643 + ERC-5192 (optionally). Off-chain: Node.js/TypeScript, viem for contract reading, Redis for access cache, JWT (jose) for sessions. Frontend: wagmi + ConnectKit for wallet connection, react-query for subscription state.

For ERC-20 payment (USDC/DAI) add Permit2 — user signs approval and renewSubscription call in one operation, no preliminary approve.

Timeline Estimates

Basic contract with ERC-5643 + backend middleware for access verification + frontend subscription management component — 3-4 days. With Permit2 payment, multi-tier access (several pricing tiers), and renewal analytics — 5-7 days.