Abstract

The following standard allows for the implementation of a standard API for subscribing to non-fungible and multi tokens. EIP-20 tokens are deposited in exchange for subscription tokens that give the right to use said non-fungible and multi tokens for a specified time limited or unlimited period.

Motivation

This standard offers a flexible, general purpose way to subscribe to the use of assets or services offered by EIP-721 or EIP-1155 contracts. From here on in, for the sake of simplicity, these contracts will be known as NFTs; the provider is the issuer of said NFTs and the subscriber(s) uses them.

This proposal was originally conceived from the want to give creators of music and film, back control. The distribution and delivery of digital content is currently the purview of centralised tech corporations who offer homogeneous subscription models to their customers. This proposal specifies a standard for dapp developers to give creators the ability to set their own custom subscription models and hence, open up new revenue streams that can lead to decentralised distribution and delivery models.

Use cases include any sort of periodic (e.g. daily, weekly, monthly, quarterly, yearly/annual, or seasonal) use of or access to assets or services such as:

  • Subscriptions for streaming music, video, e-learning or book/news services
  • Sharing of digital assets among subscribers
  • Club memberships such as health clubs
  • Season tickets for sports and e-sports
  • Agreement between parties to exchange fixed rate subscription stream with variable income in DeFi
  • Renting in-game assets
  • Etc.

The subscription token borrows a few functions from the EIP-20 specification. An implementer is free to implement the rest of the standard; allowing for example subscription tokens to be transferred in secondary markets, sent as gifts or for refunds etc.

Specification

The subscriber deposits EIP-20 to receive an NFT and subscription. Subscription tokens balance automatically decreases linearly over the lifetime of usage of the NFT, and use of the NFT is disabled once the subscription token balance falls to zero. The subscriber can top up the balance to extend the lifetime of the subscription by depositing EIP-20 tokens in exchange for more subscription tokens.

Smart contracts implementing this EIP standard MUST implement the EIP-165 supportsInterface function and MUST return the constant value true if 0xC1A48422 is passed through the interfaceID argument. Note that revert in this document MAY mean a require, throw (not recommended as depreciated) or revert solidity statement with or without error messages.

interface ISubscriptionToken {
    /**
        @dev This emits when the subscription token constructor or initialize method is
        executed.
        @param name The name of the subscription token
        @param symbol The symbol of the subscription token
        @param provider The provider of the subscription whom receives the deposits
        @param subscriptionToken The subscription token contract address
        @param baseToken The ERC-20 compatible token to use for the deposits.
        @param nft Address of the `nft` contract that the provider mints/transfers from.
        All tokenIds referred to in this interface MUST be token instances of this `nft` contract.
    */
    event InitializeSubscriptionToken(
        string name,
        string symbol,
        address provider,
        address indexed subscriptionToken,
        address indexed baseToken,
        address indexed nft,
        string uri
    );

    /**
        @dev This emits for every new subscriber to `nft` contract of token `tokenId`.
        `subscriber` MUST have received `nft` of token `tokenId` in their account.
        @param subscriber The subscriber account
        @param tokenId MUST be token id of `nft` sent to `subscriber`
        @param uri MUST be uri of the `nft` that was sent to `subscriber` or empty string
    */
    event SubscribeToNFT(
        address indexed subscriber,
        uint256 indexed tokenId,
        string uri
    );

    /**
        @dev Emits when `subscriber` deposits ERC-20 of token type `baseToken` via the `deposit method.
        This tops up `subscriber` balance of subscription tokens
        @param depositAmount The amount of ERC-20 of type `baseToken` deposited
        @param subscriptionTokenAmount The amount of subscription tokens sent in exchange to `subscriber`
        @param subscriptionPeriod Amount of additional time in seconds subscription is extended
    */
    event Deposit(
        address indexed subscriber,
        uint256 indexed tokenId,
        uint256 depositAmount,
        uint256 subscriptionTokenAmount,
        uint256 subscriptionPeriod
    );

    /**
        @return The name of the subscription token
    */
    function name() external view returns (string memory);

    /**
        @return The symbol of the subscription token
    */
    function symbol() external view returns (string memory);

    /**
        @notice Subscribes `subscriber` to `nft` of 'tokenId'. `subscriber` MUST receive `nft`
        of token `tokenId` in their account.
        @dev MUST revert if `subscriber` is already subscribed to `nft` of 'tokenId'
        MUST revert if 'nft' has not approved the `subscriptionToken` contract address as operator.
        @param subscriber The subscriber account. MUST revert if zero address.
        @param tokenId MUST be token id of `nft` contract sent to `subscriber`
        `tokenId` emitted from event `SubscribeToNFT` MUST be the same as tokenId except when
        tokenId is zero; allows OPTIONAL tokenid that is then set internally and minted by
        `nft` contract
        @param uri The OPTIONAL uri of the `nft`.
        `uri` emitted from event `SubscribeToNFT` MUST be the same as uri except when uri is empty.
    */
    function subscribeToNFT(
        address subscriber,
        uint256 tokenId,
        string memory uri
    ) external;

    /**
        @notice Top up balance of subscription tokens held by `subscriber`
        @dev MUST revert if `subscriber` is not subscribed to `nft` of 'tokenId'
        MUST revert if 'nft' has not approved the `subscriptionToken` contract address as operator.
        @param subscriber The subscriber account. MUST revert if zero address.
        @param tokenId The token id of `nft` contract to subscribe to
        @param depositAmount The amount of ERC-20 token of contract address `baseToken` to deposit
        in exchange for subscription tokens of contract address `subscriptionToken`
    */
    function deposit(
        address subscriber,
        uint256 tokenId,
        uint256 depositAmount
    ) external payable;

    /**
        @return The balance of subscription tokens held by `subscriber`.
        RECOMMENDED that the balance decreases linearly to zero for time limited subscriptions
        RECOMMENDED that the balance remains the same for life long subscriptions
        MUST return zero balance if the `subscriber` does not hold `nft` of 'tokenId'
        MUST revert if subscription has not yet started via the `deposit` function
        When the balance is zero, the use of `nft` of `tokenId` MUST NOT be allowed for `subscriber`
    */
    function balanceOf(address subscriber) external view returns (uint256);
}

Subscription token balances

An example implementation mints an amount of subscription token that totals to one subscription token per day of the subscription period length paid for by the subscriber; for example a week would be for seven subscription tokens. The subscription token balance then decreases automatically at a rate of one token per day continuously and linearly over time until zero. The balanceOf function can be implemented lazily by calculating the amount of subscription tokens left only when it is called as a view function, thus has no gas cost.

Subscription token price

Subscription token price paid per token per second can be calculated from the Deposit event parameters as depositAmount / (subscriptionTokenAmount * subscriptionPeriod)

NFT metadata

The NFT’s metadata can store information of the asset/service offered to the subscriber by the provider for the duration of the subscription. This MAY be the terms and conditions of the agreed subscription service offered by the provider to the subscriber. It MAY also be the metadata of the NFT asset if this is offered directly. This standard is kept purposely general to cater for many different use cases of NFTs.

Subscription expiry

When the subscription token balance falls to zero for a subscriber (signifying that the subscription has expired) then it is up to the implementer on how to handle this for their particular use case. For example, a provider may stop streaming media service to a subscriber. For an NFT that represents an image stored off-chain, perhaps the NFT’s uri function no longer returns back a link to its metadata.

Caveats

With some traditional subscription models based on fiat currencies, the subscribers’ saved payment credentials are used to automatically purchase to extend the subscription period, at or just before expiry. This feature is not possible in this proposal specification as recurring payments will have to have allowance approved for signed by a subscriber for each payment when using purely cryptocurrencies.

This proposal does not deal with pausing subscriptions directly, implementers can write their own or inherit off 3rd party smart contract abstractions such as OpenZeppelin’s Pausable. In that case, balanceOf method would need extra logic and storage to account for the length of time the subscription tokens were paused.

Rationale

Tokenisation of subscriptions

The subscription itself has value when it is exchanged for a deposit. This proposal enables subscriptions to be ‘tokenised’ thus secondary markets can exist where the subscription tokens can be bought and sold. For example, a fan might want to sell their season ticket, that gives access to live sporting events, on to another fan. This would not be as easily possible if there was only a date expiry extension feature added to NFTs. An implementer can simply implement the rest of the EIP-20 functions for subscription tokens to be traded. It is left to the implementer to decide if the subscription service offered is non-fungible or fungible. If non-fungible then buying the subscription tokens would simply give the same period left to expiration. If fungible and the purchaser already had an existing subscription for the same service then their total subscription period can be extended by the amount of subscription tokens bought.

Cater for current and future uses of NFTs

This proposal purposely keeps tokenId and uri optional in the subcribeToNFT method to keep the specification general purpose. Some use cases such as pre-computed image NFT collections don’t require a different ‘uri’, just a different tokenId for each NFT. However, in other use cases such as those that require legal contracts between both parties, individual uri links are probably required as the NFT’s metadata may require information from both parties to be stored on immutable storage.

Giving back users control

Traditional subscription models, particularly with streaming services, control of the subscription model is totally with that of the central service provider. This proposal gives decentralised services a standard way to give control back to their users. Hence each user is able to develop their own subscription eco system and administer it towards one that suits theirs and their subscribers’ needs.

Backwards Compatibility

A subscription token contract can be fully compatible with EIP-20 specification to allow, for example, transfers from one subscriber to another subscriber or user. EIP-20 methods name, symbol and balanceOf are already part of the specification of this proposal, and it is left to the implementer to choose whether to implement the rest of EIP-20’s interface by considering their own use case.

Use of subscription tokens is in effect an indirect way to control the lifetime of an NFT. As such it is assumed that this arrangement would work best when the NFTs and subscription token contracts subscribing to the NFTs, are deployed by the same platform or decentralised app. It MUST NOT have an impact or dependencies to existing NFTs that have not approved the subscription token as an operator. Indeed in this case, any other parties wouldn’t be aware of and any NFT lifetime dependencies will be ignored, hence should not work anyway. To this end, this proposal specifies that the ‘nft’ MUST have approved the subscriptionToken contract address as operator.

Security Considerations

It is normal for service providers to receive subscriber payments upfront before the subscriber gets to use the service. Indeed this proposal via the deposit method follows this remit. It would therefore be possible that a service provider sets up, receives the deposits and then does not provide or provides the service poorly to its subscribers. This happens in the traditional world too and this proposal does not cover how to resolve this.

The subscribeToNFT method takes a parameter uri link to the nft metadata. It is possible if stored on centralised storage that the owners can change the metadata, or perhaps the metadata is hacked which is an issue with vanilla NFT contracts too. But because the uri is provided at the time of subscription rather then deployment, it is RECOMMENDED that where the use case requires, implementers ensure that the uri link is to immutable storage.

Copyright and related rights waived via CC0.