EIP-5643 Subscription NFTs

Hi everyone! Thanks for sharing this brilliant idea ! I am Wen, I based in Paris and I am building an NFT-based subscription/ NFT gated e-commerce platform using Unlock @julien51 for the creators, e-commerce and the local business.

1 Like

I really do like the idea, what about considering a pause state, as sometimes you may want to take a holiday from a sub but not cancel, there might be a limit for how long you can pause but may encourage them to remain subscribed.

That’s a great idea @cygaar. What happens to the token ownership when the subscription has expired? What will ownerOf() return in this case for example?

1 Like

I believe that it is important to describe what users should expect from tokens implementing this.

For example, ERC721 implies that I have ownership over a token. (Of course there’s nothing preventing contracts from doing all types of things, including implementing backdoors that allow someone else to transfer my tokens, but there is an expected behaviour and anyone not following is considered shady.)

So, regarding subscriptions, what should users expect? That they loose ownership? That the token is destroyed? That some of the token utility is limited?

If it was up to me, I would propose that when a subscription expires, all approvals are canceled. What this means in practice is that I can no longer trade the token (make profit, which is exactly the occasion when a creator would collect royalties normally). It is still owned by me, I can transfer it to my vault, or an other wallet, but I can’t trade it until I renew my subscription.

1 Like

Thanks @cygaar, I was recently developping a similar subscription NFT.
I think manual is right as we should renew when we need to.
Having an NFT means you have ownership. The decision should be left to each project to decide about ownership.
It is very simple and we already think it is a good idea. What is the main problem with this EIP?

good proposal. For subscription-based NFT, I feel the auto-renew is an important feature, which is not defined in the interface. You have an interface of “isRenewable” , why not add an “isAutoRenewable” interface?

It’s really a good idea. I think it can be applied to many scenarios

This is a good point. In my mind, when a subscription expires, the user still owns the token, but the token should no longer have any utility. I don’t think the EIP should include anything about transferability after a token expires - that should be left to the implementation.

In the case of Unlock the ownerOf function will still return the owner however balanceOf returns only the number of non-expired NFT so that tools that mostly rely on this will “block” access by default.

Unlock is a special case of this, because the Unlock NFT is usually just the subscription. However if you have NFTs that represent art, owning the NFT is the utility in many cases.

1 Like

This is fantastic idea, and I really like it!

I think adding data bytes is a good idea. It will be required if one wants to renew the subscrtion with ERC20 token with permit.

I really like your idea @cygaar ! I’m working on a specification of SBT (EIP-5727). In EIP-5727, there is an extension used for token expiration. (e.g. some skill certifications are only valid for a period of time)
I think your proposal is a perfect fit for this. I’m considering to integrate EIP-5643 if it went to Final.

A couple of questions:

  • why uint64 for duration? Why limit not uint256? Seems like the flexibility is worth it.

  • after thinking about it a bit, all the interface really needs is isExpired().

The more methods, the more assumptions. On one end of the spectrum is just an interface determining validity, another is completely prescribing how subscriptions are done.

A middle ground is likely to leave a lot of people rolling their own solution.

What value is unlocked by having a standard for this? Not all subscriptions are the same today outside of Ethereum. The challenge isn’t in the technology but in the use cases.

I wonder what subscription-gateway services exist today? Mainly things like pay pal, etc.

The core value I’m not certain is in managing the subscription as much as it is enabling recurring payments. Having a solid standard for that brings wallets and thus users onboard to it.

Love the simplicity of this proposal.

I tend to agree with @aram that it could be kept even simpler by providing the read only interface to enable more flexibility with implementations.

Comments on tiers, auto-renewals and expected behaviours of NFTs are all important for implementation but less so at the interface level I would say. At least for a first subscription-based interface.

Loving the work, keep it up and let us know if you need any help.

This seems like a popular proposal, and something we have been thinking about at Verida for enabling streaming payments for storage node infrastructure providers.

What’s the status / next steps?

I’ve been pretty busy, but I did put together a library for this EIP: GitHub - cygaar/ERC5643: Subscription NFT Smart Contracts

Would you add any other read functions? I assume you’d want to remove cancel/renew

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);
    }
    ...
}
1 Like