Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gitcoin Grants Proposal v1 #20

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ See also: https://github.com/ethereum/EIPs/issues/948

* Scott Burke and Andrew Redden from BlockcrushR
* Kevin Owocki - Gitcoin
* Kevin Seagraves - Gitcoin
* Travis Mathis / Kerman Kohli - 8x Protocol
* Rober Jorden
* John Griffin
Expand Down
127 changes: 127 additions & 0 deletions standards/gitcoinGrants948.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# [WIP] ERC948 Subscription Standard (v1)

## Summary
The following is a proposed standard for subscription payments with read and write specs.

## Abstract
This standard allows a user to set a recurring payment to a service provider. This recurring transaction is ideally executed by a third party agent, but at a minimum the service provider themselves can execute the payment. Either side of the subscription should be able cancel the agreement with a reasonable amount of forewarning to the other party. This standard or similar should be integrated with future smart wallets to enable subscription payments in the Ethereum ecosystem.

## Motivation
Recurring payments are a widely use monetization method in traditional software. Currently, no such pattern exists with desirable UX traits in the decentralized space. Creating such a pattern will unlock many new avenues for revenue and provide an alternative to the dominant rent seeking and token-valuation based monetization methods in the current ecosystem.

### ERC948 Read

```
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);

}

interface ERC948Read is ERC165 {

@dev Return the details of a subscription
@param subscriptionId - A unique representation of the subscription based on the details of the agreement

function getSubscription(
bytes subscriptionId
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably meant bytes32. Var length arrays can only be passed in internal function calls. I think it would be a good idea to consider switching to uint256 (see rationale: https://github.com/EthereumOpenSubscriptions/standard/blob/99d833792ddd4d2a422fea66fb21379c7a2ef61a/standards/stef_and_luka-standard_proposal_draft.md#subscription-identifiers)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did mean bytes32. If sh3 hashes are directly convertible to unit256 then it makes sense to use them. Can you point me to resources to learn more about this?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

)
public
view
returns (
address _destination,
address _recipient,
address _agent,
uint _agentRewardPct,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is agent a processor (whoever is supposed to execute recurring payments)? I don't think this should be part of the standard - support for different business models (processors, fees, etc.) should come as an extension on top of the ERC948. Imo standard should support the simplest possible use case: recurring payments between a user and a provider (where provider takes care of recurring execution).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agent is a processor in the context of your implementation. Agent is placed here to allow for future implementations of third party processing. In this architecture, each user would be owner of their subscriptions contract. Thus, each user may choose a different agent or agent service for each subscription vs. having one processor to one provider as in your implementation. Agent may remain empty and recipient will always have the ability to pull down funds.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think that processor-specific definitions are not the part of ERC-948, this can be handled externally, take a look at ERC-1228 as an example.

uint _valuePerPeriod,
uint _secondsPerTimePeriod,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting choice, but it's impossible to set up a subscription which would be charged on the 1st of every month.

Copy link
Author

@captnseagraves captnseagraves Aug 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is impossible to have it fire on the first of each month. It is not clear to me how your implementation accomplishes this either however. I would like to learn. It appears to me that your implementation also has a rigid amount of time during each time period which is allotted on the creation of the subscription. The alternative being to supply a timestamp from an outside source. Or perhaps there is an ethereum calendar library that I am not aware of? Would you mind explaining your solution to me, or perhaps point me to resources that explain your approach? I'm happy to get on a phone call as well. Thank you.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The basic idea is to provide billing period using a specific time unit. Time unit could be an enumerated constant (eg. month(0), day(1), hour(2),...).
So for instance, billing every week would look like: (... _timeUnit=1, _period=7 ...)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same as in our proposal. I think we should distinguish the actual payment and funds availability for the subscription.
Why not leave this up to the provider on when he wants to execute the payment, like every day, every month etc.
Using valuePerPeriod enables to define the cost of subscription and required amount of funds to be available for it without a need of implementing calendar functions (not trivial in Solidity)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provider can execute the payment at his own leisure that is true. However, subscriber still needs a strong guarantee that the payment won't occur before specified dates. That would require on-chain logic.

I don't think we need a complete calendar functionality with features like time zones and daylight savings... A days-per-month mapping with logic to handle leap years and some time unit transform functions would probably suffice?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provider can execute the payment at his own leisure that is true. However, subscriber still needs a strong guarantee that the payment won't occur before specified dates. That would require on-chain logic.

_valuePerPeriod and _secondsPerTimePeriod and current timestamp are enough to calculate the amount due in any given time. I reason about the subscription as a stream of value with given rates, esentially while time flies the amount due is increasing, moreover provider/subscriber have the gurarantee to transfer only:

The `execute` method would call `token.transfer(provider, (now - last_payment_at) * tokens_per_time_unit / time_unit)` 

as we described in #16

uint _expiration,
bool _active
);

@dev Return array of all subscriptions of contract owner

function getUserSubscriptions()
public
view
returns(
subscriptions[]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an array of subscription IDs?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was written as an array of structs, which cannot be done. It should be an array of id's

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

);
}


interface ERC948Write is ERC948Read {

@dev Creates a new subscription agreement
@param _destination - Contract address of the ERC20 token that is being used for transactions
@param _recipient - Address of the recipient of funds
@param _agent - Individual, organization, or network that performs the transaction on behalf of the subscription owner and recipient
@param _agentRewardPct - Percentage of each transaction the agent will receive as reward
@param _valuePerPeriod - Amount value approved to be exchanged per time period
@param _secondsPerTimePeriod - Seconds to elapse per time period

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this support monthly subscriptions? (eg. 1st day of each month)

@param _expiration - Time when subscription expires and must be renewed to continue
@returns subscriptionId - hash of details of subscription to produce unique id

function createSubscription(
address _destination,
address _recipient,
address _agent,
uint _agentRewardPct,
uint _valuePerPeriod,
uint _secondsPerTimePeriod,
uint _expiration
)
public
returns(
bytes subscriptionId
)

@dev revokes the agents ability to perform transactions on behalf of the subscription participants
@param subscriptionId - A unique representation of the subscription based on the details of the agreement

function revokeAgent(
bytes subscriptionId
)
public;

@dev cancels the subscription so no payments are made and whatever service is provided is stopped
@param subscriptionId - A unique representation of the subscription based on the details of the agreement

function cancelSubscription(
bytes subscriptionId
)
public;

@dev executes payment as specified by subscription agreement
@dev ideally called by _agent, but should allow _recipient to call as well

function executePayment(
bytes subscriptionId
)
public;

event newSubscription(
address _destination,
address _recipient,
address _agent,
uint _agentRewardPct,
uint _valuePerPeriod,
uint _secondsPerTimePeriod,
uint _expiration,
bool _active,
bytes subscriptionId
);

event agentRevoked(bytes subscriptionId, address _agentAddress);

event cancelSubscription(bytes subscriptionId);

event paymentExecuted(address _owner, address _recipient, address _agent, int _valuePerPeriod);

}
```