-
Notifications
You must be signed in to change notification settings - Fork 15
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
) | ||
public | ||
view | ||
returns ( | ||
address _destination, | ||
address _recipient, | ||
address _agent, | ||
uint _agentRewardPct, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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). There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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),...). There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
as we described in #16 |
||
uint _expiration, | ||
bool _active | ||
); | ||
|
||
@dev Return array of all subscriptions of contract owner | ||
|
||
function getUserSubscriptions() | ||
public | ||
view | ||
returns( | ||
subscriptions[] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this an array of subscription IDs? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
||
} | ||
``` |
There was a problem hiding this comment.
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 touint256
(see rationale: https://github.com/EthereumOpenSubscriptions/standard/blob/99d833792ddd4d2a422fea66fb21379c7a2ef61a/standards/stef_and_luka-standard_proposal_draft.md#subscription-identifiers)There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a design decision, EVM works with 32 byte words (256bits)[1]. Since
bytes32
anduint256
both use 2^256 bits conversion is free[2]. SO question about conversion: https://ethereum.stackexchange.com/questions/6498/how-to-convert-a-uint256-type-integer-into-a-bytes32