EIP1337 - Subscriptions on the blockchain

# Simple Summary

Monthly subscriptions are a key monetization channel for legacy web, and arguably they are the most healthy monetization channel for businesses on the legacy web (especially when compared to ad/surveillance) based models. They are arguably more healthy than a token based economic system (depending upon the vesting model of the ICO) because

# For a user:
  • you don't have to read a complex whitepaper to use a dapps utility (as opposed to utility tokens)
  • you don't have to understand the founder's vesting schedules
  • you can cancel anytime
# For a Service Provider:
  • since you know your subscriber numbers, churn numbers, conversion rate, you get consistent cash flow, and accurate projections
  • you get to focus on making your customers happy
  • enables you to remove speculators from your ecosystem

For these reasons, we think it's imperative to create a standard way to do 'subscriptions' on Ethereum.

# Abstract

To enable replay-able transactions users sign a concatenated bytes hash that is composed of the input data needed to execute the transaction. This data is stored off chain by the recipient of the payment and is transmitted to the customers smart contract for execution alongside a provided signature.

# Motivation

Recurring payments are the bedrock of SaSS and countless other businesses, a robust specification for defining this interaction will enable a broad spectrum of revenue generation and business models.

# Specification

# Enum Contract

EIP-1337 Contracts should be compiled with a contract that references all the enumerations that are required for operation

/// @title Enum - Collection of enums
/// Original concept from Richard Meissner - <[email protected]> Gnosis safe contracts
contract Enum {
    enum Operation {
        Call,
        DelegateCall,
        Create,
        ERC20, 
        ERC20Approve
    }
    enum SubscriptionStatus {
        ACTIVE,
        PAUSED,
        CANCELLED,
        EXPIRED
    }
    
    enum Period {
        INIT,
        DAY,
        WEEK,
        MONTH
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# EIP-165

EIP-1337 compliant contracts support EIP-165 announcing what interfaces they support

interface ERC165 {
  /**
   * @notice Query if a contract implements an interface
   * @param interfaceID The interface identifier, as specified in ERC-165
   * @dev Interface identification is specified in ERC-165. This function
   * uses less than 30,000 gas.
   * @return `true` if the contract implements `interfaceID` and
   * `interfaceID` is not 0xffffffff, `false` otherwise
   **/
  function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
1
2
3
4
5
6
7
8
9
10
11

# Public View Functions

# isValidSubscription

/** @dev Checks if the subscription is valid.
  * @param bytes subscriptionHash is the identifier of the customer's subscription with its relevant details.
  * @return success is the result of whether the subscription is valid or not.
  **/

function isValidSubscription(
            uint256 subscriptionHash
        ) 
        public 
        view 
        returns (
            bool success
        )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# getSubscriptionStatus

/** @dev returns the value of the subscription
  * @param bytes subscriptionHash is the identifier of the customer's subscription with its relevant details.
  * @return status is the enumerated status of the current subscription, 0 expired, 1 active, 2 paused, 3 cancelled
  **/
function getSubscriptionStatus(
        uint256 subscriptionHash
    )
    public 
    view 
    returns (
        uint256 status, 
        uint256 nextWithdraw
    )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# getSubscriptionHash
/** @dev returns the hash of cocatenated inputs to the address of the contract holding the logic.,
  * the owner would sign this hash and then provide it to the party for execution at a later date,
  * this could be viewed like a cheque, with the exception that unless you specifically
  * capture the hash on chain a valid signature will be executable at a later date, capturing the hash lets you modify the status to cancel or expire it.
  * @param address recipient the address of the person who is getting the funds.
  * @param uint256 value the value of the transaction
  * @param bytes data the data the user is agreeing to
  * @param uint256 txGas the cost of executing one of these transactions in gas(probably safe to pad this)
  * @param uint256 dataGas the cost of executing the data portion of the transaction(delegate calls etc)
  * @param uint 256 gasPrice the agreed upon gas cost of Execution of this subscription(cost incurment is up to implementation, ie, sender or receiver)
  * @param address gasToken address of the token in which gas will be compensated by, address(0) is ETH, only works in the case of an enscrow implementation)
  * @param bytes meta dynamic bytes array with 4 slots, 2 required, 2 optional // address refundAddress / uint256 period / uint256 offChainID / uint256 expiration (uinx timestamp)
  * @return bytes32, return the hash input arguments concatenated to the address of the contract that holds the logic.
  **/
function getSubscriptionHash(
        address recipient,
        uint256 value,
        bytes data,
        Enum.Operation operation,
        uint256 txGas,
        uint256 dataGas,
        uint256 gasPrice,
        address gasToken,
        bytes meta
    )
    public
    view
    returns (
        bytes32 subscriptionHash
    )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# getModifyStatusHash
/** @dev returns the hash of concatenated inputs that the owners user would sign with their public keys
  * @param address recipient the address of the person who is getting the funds.
  * @param uint256 value the value of the transaction
  * @return bytes32 returns the hash of concatenated inputs with the address of the contract holding the subscription hash
  **/
function getModifyStatusHash(
        bytes32 subscriptionHash
        Enum.SubscriptionStatus status
    )
    public
    view
    returns (
        bytes32 modifyStatusHash
    )
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Public Functions

# modifyStatus

/** @dev modifys the current subscription status
  * @param uint256 subscriptionHash is the identifier of the customer's subscription with its relevant details.
  * @param Enum.SubscriptionStatus status the new status of the subscription
  * @param bytes signatures of the requested method being called
  * @return success is the result of the subscription being paused
  **/
function modifyStatus(
        uint256 subscriptionHash, 
        Enum.SubscriptionStatus status, 
        bytes signatures
    ) 
    public 
    returns (
        bool success
    )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# executeSubscription

/** @dev returns the hash of cocatenated inputs to the address of the contract holding the logic.,
  * the owner would sign this hash and then provide it to the party for execution at a later date,
  * this could be viewed like a cheque, with the exception that unless you specifically
  * capture the hash on chain a valid signature will be executable at a later date, capturing the hash lets you modify the status to cancel or expire it.
  * @param address recipient the address of the person who is getting the funds.
  * @param uint256 value the value of the transaction
  * @param bytes data the data the user is agreeing to
  * @param uint256 txGas the cost of executing one of these transactions in gas(probably safe to pad this)
  * @param uint256 dataGas the cost of executing the data portion of the transaction(delegate calls etc)
  * @param uint 256 gasPrice the agreed upon gas cost of Execution of this subscription(cost incurment is up to implementation, ie, sender or receiver)
  * @param address gasToken address of the token in which gas will be compensated by, address(0) is ETH, only works in the case of an enscrow implementation)
  * @param bytes meta dynamic bytes array with 4 slots, 2 required, 2 optional // address refundAddress / uint256 period / uint256 offChainID / uint256 expiration (uinx timestamp)
  * @param bytes signatures signatures concatenated that have signed the inputs as proof of valid execution
  * @return bool success something to note that a failed execution will still pay the issuer of the transaction for their gas costs.
  **/
function executeSubscription(
        address to,
        uint256 value,
        bytes data,
        Enum.Operation operation,
        uint256 txGas,
        uint256 dataGas,
        uint256 gasPrice,
        address gasToken,
        bytes meta,
        bytes signatures
    )
    public 
    returns (
        bool success
    )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# Rationale

Merchants who accept credit-cards do so by storing a token that is retrieved from a third party processor(stripe, paypal, etc), this token is used to grant access to pull payment from the cx's credit card provider and move funds to the merchant account. Having users sign input data acts in a similliar fashion and enables that merchant to store the signature of the concatenated bytes hash and input data used to generate the hash and pass them off to the contract holding the subscription logic, thus enabling a workflow that is similliar to what exists in the present day legacy web.

# Backwards Compatibility

N/A

# Test Cases

TBD

# Implementation

TBD

Copyright and related rights waived via CC0 (opens new window).

▲ Powered by Vercel