EIPs/EIPS/eip-777.md

23 KiB
Raw Blame History

eip title author discussions-to status type category created requires
777 A New Advanced Token Standard Jordi Baylina <jordi@baylina.cat>, Jacques Dafflon <jacques.dafflon@gmail.com>, Thomas Shababi <tom@truelevel.io> https://github.com/ethereum/EIPs/issues/777 Draft Standards Track ERC 2017-11-20 820

Simple Summary

This EIP defines a standard interface for token contracts.

The repository containing the reference implementation for this standard can be found at jacquesd/ERC777 and installed via npm with: npm install erc777.

Abstract

This standard defines a new way to interact with a Token Contract.

It defines operators to send tokens on behalf of another address contract or regular account. It takes advantage of ERC820 to find out whether and where to notify contracts and regular addresses when they receive tokens as well as to allow compatibility with old contracts.

Motivation

This standard tries to improve the widely used ERC20 token standard. The main advantages of this standard are:

  1. Uses the same philosophy as Ether in that tokens are sent with send(dest, value, data).
  2. Both contracts and regular address can control and reject which token they send by registering a tokensToSend function rejection is done by throwing in the function.
  3. Both contracts and regular addresses can control and reject which token they receive by registering a tokensReceived function rejection is done by throwing in the function.
  4. The tokensReceived function also avoids the double call needed in the ERC20 standard (approve / transferFrom).
  5. The token holder can "authorize" and "revoke" operators who can send tokens on their behalf. These operators are intended to be verified contracts such as an exchange, a cheque processor or an automatic charging system.
  6. Every token transaction contains a userData bytes field and a similar operatorData in case of an operator transaction to be used freely by the user (token holder) and the operator respectively to pass data to the recipient.
  7. It is backward compatible with wallets that do not contain the tokensReceived function by deploying a proxy contract for the wallet.

Specification

ERC777Token (Token Contract)

interface ERC777Token {
    function name() public constant returns (string);
    function symbol() public constant returns (string);
    function totalSupply() public constant returns (uint256);
    function granularity() public constant returns (uint256);
    function balanceOf(address owner) public constant returns (uint256);

    function send(address to, uint256 amount, bytes userData) public;

    function authorizeOperator(address operator) public;
    function revokeOperator(address operator) public;
    function isOperatorFor(address operator, address tokenHolder) public constant returns (bool);
    function operatorSend(address from, address to, uint256 amount, bytes userData, bytes operatorData) public;

    event Sent(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256 amount,
        bytes userData,
        bytes operatorData
    );
    event Minted(address indexed operator, address indexed to, uint256 amount, bytes operatorData);
    event Burned(address indexed operator, address indexed from, uint256 amount, bytes userData, bytes operatorData);
    event AuthorizedOperator(address indexed operator, address indexed tokenHolder);
    event RevokedOperator(address indexed operator, address indexed tokenHolder);
}

The token-contract MUST register the ERC777Token interface with its own address via ERC820. If the contract has a switch to enable or disable ERC777 methods, every time the switch is triggered, the token MUST register or unregister its own address accordingly the ERC777Token interface via ERC820. (Unregistering implies setting the address to 0x0.)

The basic unit token MUST be 1018.

Methods

name
 function name() public constant returns (string)

Returns the name of the token e.g. "MyToken".

returns: Name of the token


symbol
function symbol() public constant returns (string)

Returns the symbol of the token e.g. "MYT".

returns: Symbol of the token


granularity
function granularity() public constant returns (uint256)

Returns the smallest part of the token that's not divisible.

Any minting, transfer or burning of tokens MUST be a multiple of this value. Any operation that would result in a balance that's not a multiple of this value, MUST be considered invalid and the transaction MUST throw.

Most of the tokens SHOULD be fully partitionable, i.e. this function SHOULD return 1 unless there is a good reason for not allowing any partition of the token.

NOTE: granularity MUST be greater or equal to 1.

returns: The smallest non-divisible part of the token.


totalSupply
function totalSupply() public constant returns (uint256)

Get the total number of minted tokens.

returns: Total supply of tokens currently in circulation.


balanceOf
function balanceOf(address tokenHolder) public constant returns (uint256)

Get the balance of the account with address tokenHolder.

parameters

  • tokenHolder: Address for which the balance is returned

returns: Amount of token held by tokenHolder in the token contract.


send
function send(address to, uint256 amount, bytes userData) public

Send amount of tokens to address to.

This call MUST:

  • call the tokensToSend method on the contract implementing ERC777TokensSender as returned by an ERC820 lookup on the from address regardless if from is a regular address or a contract.
  • call the tokensReceived method on the address implementing ERC777TokensRecipient as returned by an ERC820 lookup on the to address regardless if to is a regular address or a contract.
  • fire the Sent event.

If to is a contract which is not prepared to receive tokens. Specifically, it is a contract which does not register an address (its own or another) via ERC820 implementing the ERC777TokensRecipient interface. Then send MUST throw.

The function MUST throw if:

  • msg.sender account balance does not have enough tokens to spend
  • to is a contract which is not prepared to receive tokens.

NOTE: Sending an amount of 0 is valid and MUST be treated as a regular send.

parameters

  • to: tokens recipient
  • amount: number of tokens transferred
  • userData: information attached to the transaction by the user (sender)

authorizeOperator
function authorizeOperator(address operator) public

Authorize a third party operator to send tokens on behalf of msg.sender.

An AuthorizedOperator event MUST be fired on a successful call to this function.

NOTE: The token holder (msg.sender) is always an operator for himself. That is, he CAN call operatorSend on himself. This right cannot be revoked. Therefore if this function is called to set the token holder (msg.sender) as operator, then the function MUST throw.

NOTE: A token holder CAN authorize multiple operators at the same time.

parameters

  • operator: Address which will be granted rights to manage the tokens.

revokeOperator
function revokeOperator(address operator) public

Revoke a third party operator's rights to send tokens on behalf of msg.sender.

A RevokedOperator event MUST be fired on a successful call to this function.

NOTE: The token holder (msg.sender) is always an operator for himself. That is, he CAN call operatorSend on himself. This right cannot be revoked. Therefore if this function is called to set the token holder (msg.sender) as operator, then the function MUST throw.

parameters

  • operator: Address which whose rights to manage the tokens will be revoked.

isOperatorFor
function isOperatorFor(address operator, address tokenHolder) public constant returns (bool)

Check whether the operator address is allowed to send the tokens held by the tokenHolder address.

parameters

  • operator: address to check if it has the right to manage the tokens.
  • tokenHolder: address which holds the tokens to be managed.

operatorSend
function operatorSend(address from, address to, uint256 amount, bytes userData, bytes operatorData) public

Send amount of tokens on behalf of the address from to the address to.

msg.sender MUST be an authorized operator or the owner for the from address.

This call MUST:

  • call the tokensToSend method on the contract implementing ERC777TokensSender as returned by an ERC820 lookup on the from address
  • call the tokensReceived method on the contract implementing ERC777TokensRecipient as returned by an ERC820 lookup on the to address
  • fire the Sent event

If to is a contract which is not prepared to receive tokens. Specifically, it is a contract which does not register an address (its own or another) via ERC820 implementing the ERC777TokensRecipient interface. Then operatorSend MUST throw.

The method MUST throw if:

  • from account balance does not have enough tokens to spend.
  • to is a contract which does not register an address (its own or another) via ERC820 which implement the ERC777TokensRecipient interface.
  • to is a contract which is not prepared to receive tokens.
  • msg.sender is not an authorized operator for from.

parameters

  • from: token holder (sender)
  • to: tokens recipient
  • amount: number of tokens transferred
  • userData: information attached to the transaction, previously provided by the sender (from address).
  • operatorData: information attached to the transaction by the operator

NOTE: The operator is free to pass any data via the operatorData parameter but the userData parameter is reserved for data provided by the user (token holder). The token holder SHOULD provide this data to the operator beforehand.


Events

Sent
event Sent(address indexed operator, address indexed from, address indexed to, uint256 amount, bytes userData, bytes operatorData)

Indicate a transaction of amount of tokens from the from address to the to address.

This event MUST be fired on a successful call to send and operatorSend.

parameters

  • operator: address which triggered the transfer, either the token holder for a direct send or an authorized operator for operatorSend
  • from: token holder
  • to: tokens recipient
  • amount: number of tokens sent
  • userData: information attached to the transaction by the token holder
  • operatorData: information attached to the transaction by the operator

Minted
event Minted(address indexed operator, address indexed to, uint256 amount, bytes operatorData)

Indicate the minting of amount of tokens to the to address.

This standard does not enforce a specific way to mint tokens as this can be done in various ways depending on the use case of the tokens.

However, this event MUST be fired every time tokens are minted and credited to a to recipient address. A Sent event (even with the 0x0 as from address) MUST NOT be fired to indicate minting.

parameters

  • operator: address which triggered the minting
  • to: tokens recipient
  • amount: number of tokens minted
  • operatorData: information attached to the minting by the operator
tokensReceived for minting

Every mint MUST call tokensReceived on the address implementing ERC777TokensRecipient for the to address as returned by an ERC820 lookup.

Minting MUST follow the same rules as send/operatorSend with the exception that tokensToSend MUST NOT be called in any case on any address. In addition, if to is a contract which is not prepared to receive tokens. Specifically, it is a contract which does not register an address (its own or another) via ERC820 implementing the ERC777TokensRecipient interface. Then the minting MUST throw.

The from parameter of tokensReceived MUST be 0x0. The operator MUST be msg.sender. The userData MUST be empty. operatorData MAY contain data related to the minting.


Burned
event Burned(address indexed operator, address indexed from, uint256 amount, bytes userData, bytes operatorData)

Indicate the burning of amount of tokens from the from address.

This standard does not enforce a specific way to burn tokens as this can be done in various ways depending on the use case of the tokens.

However, this event MUST be fired every time tokens are burned and taken from a from recipient address. A Sent event (even with the 0x0 as to address) MUST NOT be fired.

parameters

  • operator: address which triggered the minting
  • from: token holder
  • amount: number of tokens burned
  • userData: information attached to the burn by the token holder
  • operatorData: information attached to the burn by the operator
tokensToSend for burning

Every burn MUST call tokensToSend on the address implementing ERC777TokensSender for the from address as returned by an ERC820 lookup.

Burning MUST follow the same rules as send/operatorSend with the exception that tokensReceived MUST NOT be called in any case on any address. But if the from address register an address via ERC820 implementing the ERC777TokensSender interface, tokensToSend MUST be called.

The to parameter of tokensToSend MUST be 0x0. The operator MUST be msg.sender. The userData MUST be empty. operatorData MAY contain data related to the minting.


AuthorizedOperator
event AuthorizedOperator(address indexed operator, address indexed tokenHolder)

Indicate that the operator address is allowed to send the token of (i.e. is an operator for) the address tokenHolder.

This event MUST be fired on a successful call to authorizeOperator.

parameters

  • operator: Address which is granted rights to manage the tokens.
  • tokenHolder: address which holds the tokens to be managed.

RevokedOperator
event RevokedOperator(address indexed operator, address indexed tokenHolder)

Indicate that the operator address is denied the rights to send the token of (i.e. is not operator for) the address tokenHolder.

This event MUST be fired on a successful call to revokeOperator.

parameters

  • operator: Address which is denied the rights to manage the tokens.
  • tokenHolder: address which holds the tokens to be managed.

ERC777TokensSender

Any address (contract or regular account) CAN register a contract (itself or another) implementing the ERC777TokensSender interface via the ERC820 registry.

interface ERC777TokensSender {
    function tokensToSend(
        address operator,
        address from,
        address to,
        uint value,
        bytes userData,
        bytes operatorData
    ) public;
}

Methods

tokensToSend
function tokensToSend(address operator, address from, address to, uint value, bytes userData, bytes operatorData) public

Notify the transmission of amount of tokens from the from address.

parameters

  • operator: address which triggered the transfer, either sender for a direct send or an authorized operator for operatorSend
  • from: token holder (sender)
  • to: tokens recipient (or 0x for burning)
  • amount: number of tokens sent or burned
  • userData: information attached to the transaction by the sender
  • operatorData: information attached to the transaction by the operator
Burning Versus Sending

When tokens are sent as a result of burning:

  • to MUST be 0x0
  • userData MUST be empty
  • operator MUST be the address which initiated the burning
  • operatorData MAY contain data

When tokens are sent as a result of sending (send or operatorSend):

  • to MUST be the address to which the tokens will be sent
  • to MUST NOT be 0x0

If it is a direct send (i.e. not an operatorSend) the operator MUST be the address from which the tokens originate. That is the from and operator addresses MUST be equals.

ERC777TokensRecipient

Any address (contract or regular account) CAN register a contract (itself or another) implementing the ERC777TokensRecipient interface via the ERC820 registry.

interface ERC777TokensRecipient {
    function tokensReceived(address operator, address from, address to, uint amount, bytes userData, bytes operatorData) public;
}

Methods

tokensReceived
function tokensReceived(address operator, address from, address to, uint amount, bytes userData, bytes operatorData) public

Notify the transmission of amount of tokens to the to address.

parameters

  • operator: address which triggered the transfer, either sender for a direct send or an authorized operator for operatorSend
  • from: token holder (sender or 0x for minting)
  • to: tokens recipient (or 0x for burning)
  • amount: number of tokens sent, minted or burned
  • userData: information attached to the transaction by the sender
  • operatorData: information attached to the transaction by the operator
Minting Versus Sending

When tokens are received as a result of minting:

  • from MUST be 0x0
  • userData MUST be empty
  • operator MUST be the address which initiated the minting
  • operatorData MAY contain data.

When tokens are received as a result of sending (send or operatorSend):

  • from MUST be the one from which the tokens originate and
  • from MUST NOT be 0x0.

If it is a direct send (i.e. not an operatorSend) the operator MUST be the address from which the tokens originate. That is the from and operator addresses MUST be equals.

Image  beige logo  white logo  light grey logo  dark grey logo  black logo
Color beige white light grey dark grey black
Hex #C99D66 #FFFFFF #EBEFF0 #3C3C3D #000000

The logo MUST NOT be used to advertise, promote or associate in any way technology such as tokens which is not ERC777 compliant.

The logo for the standard can be found in the /assets/eip-777/logo folder in svg and png formats. The png version of the logo offers a few sizes in pixels. If needed, other sizes CAN be created by converting from svg into png.

ERC777 token contract authors CAN create a specific logo for their token based on this logo.

Rationale

This standard solves some of the problems of the EIP223 and goes a step further by allowing operators (generally contracts) that can manage the tokens in the same way that the ERC20 with infinite approve was allowed.

Also, the usage of ERC820 allows backward compatibility with wallets and proxy contracts without having to be redeployed.

Backward Compatibility (ERC20Token)

This EIP does not introduce backward incompatibilities and is compatible with the older ERC20 token standard.

This EIP does not use transfer and transferFrom and uses send and operatorSend to avoid mistakes in knowing which interface you are using.

This standard allows the implementation of ERC20 functions transfer, transferFrom, approve and allowance alongside to make a token compatible with ERC20.

The token CAN implement decimals() for backward compatibility. If implemented, it MUST always return 18.

Therefore a token contract CAN implement both ERC20 and ERC777 in parallel. Read-only functions (such as name, symbol, balanceOf, totalSupply) and internal data (such as the mapping of balances) overlap without problems. Note however that the following functions are mandatory in ERC777 and MUST be implemented: name, symbol balanceOf and totalSupply (decimal is not part of the ERC777 standard).

The write methods from both standards are decoupled and can operate independently from each other. Note that ERC20 functions SHOULD be limited to only being called from old contracts.

If the token implements ERC20, it MUST register the ERC20Token interface with its own address via ERC820. If the contract has a switch to enable or disable ERC20 methods, every time the switch is triggered, the token MUST register or unregister its own address accordingly the ERC20Token interface via ERC820. (Unregistering implies setting the address to 0x0.)

The only difference for new contracts implementing ERC20 is that registration of ERC777TokensSender and ERC777TokensRecipient via ERC820 takes precedence over ERC20. This means that even with an ERC20 transfer call, the token contract MUST check via ERC820 if the from / to address implements tokensToSend / tokensReceived and call it if available. Note that when calling ERC20 transfer on a contract, if the contract does not implement tokensReceived, the transfer call SHOULD still be accepted even if this means the tokens will probably be locked.

The table below summarizes the different actions the token contract must take when sending, minting and transferring token via ERC777 and ERC20:

ERC820 to address ERC777 send/operatorSend and Minting ERC20 transfer
ERC777TokensRecipient
registered
regular address MUST call tokensReceived
contract
ERC777TokensRecipient
not registered
regular address SHOULD accept SHOULD accept
contract MUST throw

There is no particular action to take if tokensToSend is not implemented. The transfer MUST proceed and be canceled only if another condition is not respected such as lack of funds, a throw in tokensReceived (if present) or in some specific cases if tokensReceived is not implemented.

Test Cases

The repository with the reference implementation contains all the tests.

Implementation

The repository at jacquesd/ERC777 contains the reference implementation.

Copyright and related rights waived via CC0.