Skip to content

Instantly share code, notes, and snippets.

@frozeman frozeman/token.md
Last active Apr 24, 2019

Embed
What would you like to do?
Token proposal

This is outdated: The ERC-20 is here: https://github.com/ethereum/EIPs/issues/20

Token

Methods

totalSupply

function totalSupply() constant returns (uint256 supply)

Get the total coin supply

balanceOf

function balanceOf(address _address) constant returns (uint256 balance)

Get the account balance of another account with address _address

transfer

function transfer(address _to, uint256 _value) returns (bool _success)

Send _value amount of coins to address _to

transferFrom

function transferFrom(address _from, address _to, uint256 _value) returns (bool success)

Send _value amount of coins from address _from to address _to

The transferFrom method is used for a "direct debit" workflow, allowing contracts to send coins on your behalf, for example to "deposit" to a contract address and/or to charge fees in sub-currencies; the command should fail unless the _from account has deliberately authorized the sender of the message via some mechanism; we propose these standardized APIs for approval:

approve

function approve(address _address) returns (bool success)

Allow _address to direct debit from your account with full custody. Only implement if absolutely required and use carefully. See approveOnce below for a more limited method.

unapprove

function unapprove(address _address) returns (bool success)

Unapprove address _address to direct debit from your account if it was previously approved. Must reset both one-time and full custody approvals.

isApprovedFor

function isApprovedFor(address _target, address _proxy) constant returns (bool success)

Returns 1 if _proxy is allowed to direct debit from _target

approveOnce

function approveOnce(address _address, uint256 _maxValue) returns (bool success)

Makes a one-time approval for _address to send a maximum amount of currency equal to _maxValue

isApprovedOnceFor

function isApprovedOnceFor(address _target, address _proxy) returns (uint256 maxValue)

Returns _maxValue if _proxy is allowed to direct debit the returned maxValue from address _target only once. The approval must be reset on any transfer by _proxy of _maxValue or less.

Events

Transfer

event Transfer(address indexed _from, address indexed _to, uint256 _value)

Triggered when tokens are transferred.

AddressApproval

event AddressApproval(address indexed _address, address indexed _proxy, bool _result)

Triggered when an _address approves _proxy to direct debit from their account.

AddressApprovalOnce

event AddressApprovalOnce(address indexed _address, address indexed _proxy, uint256 _value)

Triggered when an _address approves _proxy to direct debit from their account only once for a maximum of _value

@romanman

This comment has been minimized.

Copy link

romanman commented Nov 17, 2015

@frozeman : how you set which contract manage the concrete token ?

@chriseth

This comment has been minimized.

Copy link

chriseth commented Nov 17, 2015

It might be important to allow approvals to be transferred: https://www.reddit.com/r/ethereum/comments/3rk88p/cheques_for_ethereum/
In a modular contract, the contract requesting the transfer might not be the same as the one actually executing it (and its address might not even be known beforehand).

@simondlr

This comment has been minimized.

Copy link

simondlr commented Nov 17, 2015

Looks good to me. I've been building a standard token contracts (normal & library) version off this that I will post for comments today or tomorrow.

5 things.

  1. Probably not that important, but slight naming convention issues. You use "success" in transfer & "_success" in approval. Just change all to "_success"?

  2. In the events you can't name a variable "address". ie "address indexed address". It's a compiler error. I just changed all variables in events to also conform to the "_variable" pattern. Might have to eventually consult the upcoming Solidity style guide that Piper introduced. But from a technical perspective it won't change really.

  3. Christian proposed a "cheque" feature. It's similar to approveOnce, but you allow the possibility to exchange the permission of it. ie, like a normal transferrable cheque. It is novel and could be useful, but not entirely sure if we should add this complexity yet.

(EDIT: I see @chriseth commented on this above me. I guess we can add it, but not sure how this code would look at this point).

  1. There needs to be a totalAmount() API. The token contract itself can interpret what it means (the total ever created, total in circulation, total that is liquid, etc). This is important for scenarios where you need to determine the portion of token ownership.
function totalAmount() constant returns (uint256 _total)
  1. How do we do dividends? Is this the responsibility of the token contract to retrieve all balances? Or a controller on top of it?

Keen to hear more thoughts!

@alexvandesande

This comment has been minimized.

Copy link

alexvandesande commented Nov 17, 2015

This is not a part of the token functions but actually the wallet but I would argue that it would be very useful if all tokens executed a common function on the recipient address after sending to it:

recipient.sendReceipt(_from, _balance);

If the recipient is an address it would be simply ignore it, but if the recipient is a wallet or some kind of proxy contract then it could be aware that a transfer was made to it and the client could respond appropriately. In the case of the Mist/Wallet it would allow your wallet to be aware of any token transferred into it and allow you to automatically watch these incoming tokens.

@romanman

This comment has been minimized.

Copy link

romanman commented Nov 17, 2015

@alexvandesande :: what do you mean : not part of the token functions ?
are we going to be able to deal with the tokens outside the wallet ?

@aakilfernandes

This comment has been minimized.

Copy link

aakilfernandes commented Nov 17, 2015

I'm not sure I understand why approvals should be in the spec. I think it makes more sense to put access control into a separate "custodian" contract.

@frozeman

This comment has been minimized.

Copy link
Owner Author

frozeman commented Nov 17, 2015

@simondlr i incorporated most of your suggestions, thanks.
I made all return values not use _variable, as they are return values.

Im not sure about the Event params though.

For the ABI the param names don't matter, only the param types.

@frozeman

This comment has been minimized.

Copy link
Owner Author

frozeman commented Nov 17, 2015

@alex idea is that currently you have as a wallet contract no way to know when something is send for you. Like a token moved in behalf of your address.

Thats why he is suggesting to let ever token transfer try to call the sendReceipt function on a contract, so the contract can fire an event to the user, that something was send in his name.

@chriseth if the address wouldn't have this function, or would be a normal address would this fail the execution?

@ethers

This comment has been minimized.

Copy link

ethers commented Nov 17, 2015

I like the suggestions of:

  • to allow approvals to be transferred
  • recipient.sendReceipt(_from, _balance)

There are also discussions of adding an _identifier parameter to the APIs, to help dApps and future use cases that create a lot of tokens.

That said, we should keep in mind this first "standard" is not the end of it all. There can be extensions and it is up to implementers of wallets and exchanges, which ones they want to support.
This gist is well titled "Token proposal", and fits more with the IETF's RFCs (standard is not in the name).

As ETP1 (Ethereum Token Proposal1), 090ae32041bcfe120824 seems reasonably well vetted and scoped.

EDIT: I suggest this token proposal could be ERC1 (Ethereum Request for Comments 1) ethereum/EIPs#16

@simondlr

This comment has been minimized.

Copy link

simondlr commented Nov 17, 2015

@alexvandesande

Interesting idea around receipts. This is something which @SilentCicero & I have spoken about as well (calling it hooks). You ideally want a service to be notified. What would happen if a service did not implement that function? You would then still expend gas costs? Perhaps there's a more elegant way? I was thinking that these kind of notifications would depend on the application using them. ie, let's think of EtherEx. You send some tokens to it, so that it can be traded on there. You can either do 1) send tokens, and then 2) notify EtherEx through a hook that it received a deposit.

The other option is to do the approval tango. You authorise EtherEx for your address. Then on EtherEx's app page, if you use the same address, it can check for a token you specify if it is approved for a deposit. Then you can do: deposit tokens, upon which EtherEx calls transferFrom for the once off approval. Currently, I'm for the latter since it is more generic than sending a receipt each time?

@aakilfernandes

Wouldn't you then have to send tokens to the custodian to manage approvals for you (basically creating a new ledger inside the approval custodian contract)? Seems a bit more complicated, as whomever is interacting with the token contract will need to know where the custodian is if it wants this functionality. It can be separated, but I think it's generic enough to be included at this level.

@frozeman: Thanks!

@frozeman

This comment has been minimized.

Copy link
Owner Author

frozeman commented Nov 17, 2015

Concerning multiple tokens in one contract contract, we could just add a new function like transferTokenFrom and transferToken which would then receiver a token id, as the first argument, and otherwise match the transferFrometc.
@vbuterin what you think?

@frozeman

This comment has been minimized.

Copy link
Owner Author

frozeman commented Nov 17, 2015

BTW maybe the ERCs are better than her, as we can't quote people here i guess.
But in issues they will be notified.

@alexvandesande

This comment has been minimized.

Copy link

alexvandesande commented Nov 17, 2015

@aakilfernandes I kinda agree with you there but some people might see it differently. Maybe the whole approval model should be left off the official specs and we simply see what everyone else comes up with?

@simondlr It's a good point that you are incurring in extra gas costs for 99% of the uses where the token is being sent to a address or a contract that might not have the hook implemented. But how costly is that? Is there a better way? Because it solves a real problem

@simondlr

This comment has been minimized.

Copy link

simondlr commented Nov 17, 2015

@frozeman @ethers the problem with adding identifiers is that 1) it costs more to deploy, where most token types won't use it as much, 2) it adds additional complexity. If a normal transfer without identifier is called, should it map to the identifier system? If so, should it default to 0 as the identifier? Or contract address? If it creates separate balances (one with identifiers & one without), then that's a lot of overhead for a standard token to deploy each time?

@alexvandesande I'm not sure how costly it is, but it seems we can have a more elegant solution? The approval tango seems to be fine for things like exchanges (since you can accept that a deposit can be made). For wallets, it can still work like this as well. You approve the wallet to withdraw funds and then a tx is sent to allow it to withdraw funds to the wallet account. Not ideal from a UX perspective though (unless it's all hidden away). You have to issue more than one transaction. The other option is that dapps who work with tokens have an "audit" or "check" button to see if funds have been received so that their internal ledgers can be updated (or whatever else needs to happen).

I guess I'm not sure what the best solution is here. I'd like it to be separated, and not have token contracts prodding recipients the whole time. What do you think?

There are potentially cues we can take from Bitcoin & Ether itself, in the sense that you don't actually send it to someone else. You only allow a new person an approval to send it onwards... I'll have to think some more.

@frozeman

This comment has been minimized.

Copy link
Owner Author

frozeman commented Nov 17, 2015

I don't see the point of the approval functions honestly..
Why not just simply transfer tokens to some other address. I think this is overly complicated for this early stage

@MrChico

This comment has been minimized.

Copy link

MrChico commented Nov 17, 2015

I'm also a fan of skipping the approval functions and keeping things as simple

@niran

This comment has been minimized.

Copy link

niran commented Nov 17, 2015

The approval functionality turns many two-transaction scenarios into one-transaction scenarios. Consider Gnosis. As a user in 2016, I have seen the Gnosis contract code and understand that it cannot steal my money. Ethereum guarantees that the contract is immutable, so it is safe for me to give this contract access to my funds. When I want to buy shares, the Gnosis contracts can transfer the amount of funds I specified directly into the pool for the desired prediction market. Without approval, I'd have to transfer funds to Gnosis then buy shares.

The latter flow requires every contract that needs user funds to build its own bank of funds. The bookkeeping for who owns which tokens shouldn't have to be replicated in every contract. Putting approval logic lets contracts assume they can draw funds from the user's token balance without having to do any bookkeeping. If the transfer succeeds, the transaction proceeds. If the transfer fails, the transaction fails. The contracts become considerably simpler.

Yes, approval can be replaced by transferring funds to a contract instead. It is significantly more cumbersome and trivial to avoid.

@niran

This comment has been minimized.

Copy link

niran commented Nov 17, 2015

On the other hand, it'd be fine to consider transfer, balanceOf, and the Transfer event as EEP N, then separate the approval parts into EEP N+1. Gnosis and other apps can just declare that they require tokens that implement EEP N and N+1, and we can move on. Token authors want dapps to use their tokens, so I expect that they'll implement both.

EEP = Ethereum Enhancement Proposal, like Python's PEPs. EIP (like BIP) is too hard to say out loud. Maybe that ship has already sailed.

@aakilfernandes

This comment has been minimized.

Copy link

aakilfernandes commented Nov 17, 2015

I'm not really following the example. Is Gnosis or MyBank implementing the token spec?

Without approval, I'd have to transfer funds to Gnosis then buy shares.

I don't understand this. Can't I do GnosisContract.buy('XYZ',{value:X})?

@niran

This comment has been minimized.

Copy link

niran commented Nov 18, 2015

Can't I do GnosisContract.buy('XYZ',{value:X})?

That only works for ether. The approval APIs give tokens similar abilities.

Is Gnosis or MyBank implementing the token spec?

MyDollar is implementing the token spec. Gnosis will have some markets that are denominated in MyDollars, so it needs some of the trader's MyDollar funds.

@ramvi

This comment has been minimized.

Copy link

ramvi commented Nov 18, 2015

Many sub currencies will implement some kind of mining. Maybe the standard should say something about that. Either an Event for mining or an agreement that event Transfer from 0x0 is mining

@frozeman

This comment has been minimized.

Copy link
Owner Author

frozeman commented Nov 18, 2015

I added the totalSupply function

@frozeman

This comment has been minimized.

Copy link
Owner Author

frozeman commented Nov 18, 2015

One important point is still missing. And this is if we should include the token name, symbol and decimal places into the contract as a public "var".

I definitely think that the decimal places belong there, I'm ok with leaving the name and symbol out.

@vbuterin

This comment has been minimized.

Copy link

vbuterin commented Nov 18, 2015

Some kind of approve, approveOnce or cheque functionality is pretty essential. The basic use case is that contracts will sometimes want to charge fees in subcurrencies, and the callee needs a way to verify that a payment from the caller to the callee was made, and that this payment cannot be used for two calls. The simplest way of doing that is to have this approval/cheque mechanism, and have the callee cash out the cheque. There are other approaches, but their complexity is equal or greater.

I'm fine with the idea of making cheques/approvals transferable.

A few more questions:

  • Does approveOnce approve only one transfer of maximum value _maxValue, or does it approve multiple transfers of maximum total value _maxValue?
  • If the latter, do we even need approve, when we can just do approveOnce(2**100)? I'd consider making approve optional in the spec.
  • Naming preferences? The two choices seem to be approve/approveOnce/isApprovedFor vs something to do with "cheques"
  • Do we want isApprovedFor, or do we want a simple howMuchApproved() function that returns the amount approved?
@frozeman

This comment has been minimized.

Copy link
Owner Author

frozeman commented Nov 18, 2015

I would rather go with the cheque terminology and would also change the transferFrom to useCheque or so.
I also agree that we don't need an approve function, if the createCheque (approveOne) is enough.

The simpler the better

@simondlr

This comment has been minimized.

Copy link

simondlr commented Nov 18, 2015

Yeah, I'm with @vbuterin & @niran. Having approval inside the token contract eases up several scenarios such as what was described above (instead of having multiple contracts doing multiple approval tangos).

@vbuterin: There was some ambiguity initially around approveOnce. The current standard was changed so that it only allows 1 time transfer of up to maxValue. Essentially a deposit.

Full custodian approval does seem somewhat questionable & not entirely sure where you will need that. Since, you might as well just send funds to another contract to deal with it?

Perhaps... we change approve() to being the latter approveOnce() as you mentioned. Do we want to specify amount of withdrawals that can be made? Perhaps in subscription scenarios? In most circumstances though, I feel having up to value works for most use cases, even for things like subscriptions. Just leave the right amount in there for a certain time period (for example).

So, we drop approveOnce() and approve() becomes:

function approve(address _address, uint256 _maxValue) returns (bool success)

Which approves _address to withdraw anything up to _maxValue. Thoughts? Regarding transfers of approvals? How would that work? Is it something we want in this current spec at this point in time?

@frozeman

I like useCheque. So perhaps it should then be that we drop approve...

createCheque(address _address, _maxValue)

which is equivalent to allowing _address to withdraw up to _maxValue.

And then? How do we transfer cheques?

@simondlr

This comment has been minimized.

Copy link

simondlr commented Nov 18, 2015

The only problem with the terminology of cheques is that it seems to be once offs. Not "use many times until all of it has been cashed in".

So: approve, approveOnce & transferFrom gets replaced with createCheque, useCheque & transferCheque (last one tbd)? Where a cheque allows recipient to withdraw multiple times until funds are empty?

@frozeman

This comment has been minimized.

Copy link
Owner Author

frozeman commented Nov 18, 2015

So basically we get rid of the prove stuff and simple add:

function createCheque(address _address, uint256 _maxValue)
function cashCheque(address _from, address _to, uint256 _maxValue)

This needs first to allow the check receiver and then he can cash in. So technically the same as approveOnce, but more clear imo.

event CreatedCheque(address indexed _for, address indexed _address, uint256 _maxValue)
@simondlr

This comment has been minimized.

Copy link

simondlr commented Nov 18, 2015

Would this allow proportional cashing of the cheque (such as what @vbuterin described)? ie, if a cheque is created, then you can cash the same one twice, as long as it still fits into the allotted value. So it should perhaps then more read:

function cashCheque(address _from, address _to, uint256 _value)

?

@frozeman

This comment has been minimized.

Copy link
Owner Author

frozeman commented Nov 18, 2015

true

@tgerring

This comment has been minimized.

Copy link

tgerring commented Nov 18, 2015

A UI preview of this proposal is available at https://tgerring.github.io/abi2html-js/?SIGNATURE_GIST=090ae32041bcfe120824. You can ignore the "Could not connect to Ethereum client" message, as ABI-to-UI does not require a node.

@vbuterin

This comment has been minimized.

Copy link

vbuterin commented Nov 18, 2015

Yeah, createCheque and cashCheque as above, plus transferCheque(address _from, address _to, uint256 _value) sounds good. In that case, we should probably remove the _to argument from cashCheque; generally, you can only cash cheques from your own bank account.

We probably also want getChequeValue(address _from, address _for). We then have a choice of whether we want to keep the value argument in cashCheque rather than simply only allowing cashing in 100% of whatever is in there. If we want to fully follow the cheque analogy, this triad seems most intuitive to me:

  • function createCheque(address _for, uint256 _maxValue)
  • function cashCheque(address _from)
  • function getChequeValue(address _from, address _for)

Question: does running createCheque twice add the value of the two cheques together? Are there legitimate use cases for creating a cheque multiple times and then cashing either once or multiple times?

@nmushegian

This comment has been minimized.

Copy link

nmushegian commented Nov 18, 2015

All the functions that return uint should return (uint, bool) instead. You can easily make up scenarios where a 0 return value is ambiguous and significant. Is there any other simpler pattern for handling this?

@niran

This comment has been minimized.

Copy link

niran commented Nov 18, 2015

I think the value parameter is useful in cashCheque. It absolves callers from having to verify that the amount they needed was provided, and from having to refund amounts greater than what was needed. cashCheque would only succeed if the provided value was remaining in the cheque.

@niran

This comment has been minimized.

Copy link

niran commented Nov 18, 2015

Also, I think using createCheque(2**100) for the approve use case is going to lead to less clear code. It gets better if you make the magic number a constant, like createCheque(UNLIMITED_CHEQUE_VALUE), but lots of people won't do that. I think it's worth having a createBlankCheque or something for the approve scenario. Most tokens will use the TokenLib to handle all of the cheque logic, so it doesn't really make things worse for token authors.

@ethers

This comment has been minimized.

Copy link

ethers commented Nov 19, 2015

per obscuren's suggestion on Gitter, created this issue for further discussion
ethereum/EIPs#19

@caktux

This comment has been minimized.

Copy link

caktux commented Nov 19, 2015

I also think there is a problem with the terminology of cheques since they imply one-offs. Cheques are also unique, and here cheques wouldn't return unique IDs or anything; those are merely approval methods for transfers using internal bookkeeping. I think the current approve/transfer terminology is accurate and simple enough, instead of ending up with a mix of transfer and cashCheque. Would we change unapprove to tearCheque? There's also that ambiguity of cheques adding up, where approvals more clearly override a previous one.

In the use case described by Vitalik of contracts charging fees in subcurrencies, it could easily cost more to have to call approveOnce each time (if we replace the current approve method with it) than the actual fee in subcurrency. For that reason I think we should keep both approve and approveOnce, but we could add the _maxValue argument to the former, that way subscriptions or fees could be taken in multiple calls but only up to a certain amount. Another reason to keep the approval terminology, as I think it's much simpler to describe approve and approveOnce than some createMultiCheque and createCheque. Regarding isApprovedFor, it would have to return the approved amount if we do add _maxValue, just as isApprovedOnceFor does.

@frozeman I'd also like to discuss the addition of decimals, token name and symbol sooner than later. It could really simplify the task of getting a token's basic information into wallets and other dApps, and I think it does belong in a token's contract before a registry.

@frozeman

This comment has been minimized.

Copy link
Owner Author

frozeman commented Nov 19, 2015

Its pretty hard to keep up with the agreement. I will move this discussion into an ERC in the comments and we can continue there. The advantage is that when we quote each other we get notified via email etc.

Cheque is a very british/american thing, so an european is not so familiar with the details about cheques. Though i thought it sounded a bit more clear than approve.

Please continue here: --------->>>>>>>>>>> ethereum/EIPs#20

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.