EIP-5643 Subscription NFTs

instead of going through the route of manual cancellation. i believe the token can be considered automatically as being expired by checking that the expiration timestamp has been bypassed, so at that moment, any user would be able to claim ownership of that subscription through renewSubscription. For the ownerOf and balanceOf, they would not to be updated unless someone claim ownership, for ownerOf we may resolve it by checking that if it has already expired to return a zero address. but the only downside is that the balanceOf for the previous owner, it would still reflect as if he still owns it. until someone renew it, then it would adjust it accordingly. I think maybe there might be a need to add a separate subscribe function which should take care of setting up the timestamp and minting process. since subscribe would mean that we want to mint a new subscription, the renew would take care of renewing existing ones.

I originally had a subscribe function in the interface, but I felt that there would be too much variability between different subscribe use cases to force a single function signature.

makes sense to keeps it simple, in terms of renewSubscription, maybe it would be much better if it takes in the number of periods to subscribe for. (e.g: 1 month per period). so it would make it much easier to subscribe without the need to manually validate the entered expiration each time. also, maybe it’s better to use uint256 instead of uint64, since in the mapping there is no other parameters to be tightly packed with it.

interface IERC5643 {
    /// @notice Renews the subscription to an NFT
    /// Throws if `tokenId` is not a valid NFT
    /// @param tokenId The NFT to renew the subscription for
    /// @param quantity The number of periods to subscribe for
    function renewSubscription(uint256 tokenId, uint256 quantity) external payable;
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./IERC5643.sol";

contract ERC5643 is ERC721, IERC5643 {
    mapping(uint256 => uint64) private _subscriptions;
    uint256 public constant period = 30.42 days;

    constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {}

    function renewSubscription(uint256 tokenId, uint256 quantity) external payable {
        require(_isApprovedOrOwner(msg.sender, tokenId), "Caller is not owner nor approved");
        require(quantity > 0, "Zero quantity not allowed");
        uint256 expiration = block.timestamp + period * quantity;
        _subscriptions[tokenId] = expiration;
        emit SubscriptionUpdate(tokenId, expiration);