EIPs/EIPS/eip-777.md

545 lines
23 KiB
Markdown
Raw Normal View History

---
eip: 777
title: A New Advanced Token Standard
author: Jordi Baylina <jordi@baylina.cat>, Jacques Dafflon <jacques.dafflon@gmail.com>, Thomas Shababi <tom@truelevel.io>
discussions-to: https://github.com/ethereum/EIPs/issues/777
status: Draft
type: Standards Track
category: ERC
created: 2017-11-20
requires: 820
---
## Simple Summary
This EIP defines a standard interface for token contracts.
*The repository for this standard containing the reference implementation can be found at [jacquesd/ERC777](https://github.com/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](https://github.com/ethereum/EIPs/issues/820) 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](https://github.com/ethereum/EIPs/issues/20) 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 backwards compatible way with wallets that do not contain the `tokensReceived` function by deploying a proxy contract for the wallet.
## Specification
### ERC777Token (Token Contract)
``` solidity
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) public;
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 via ERC820.
The basic unit token MUST be 10<sup>18</sup>.
#### Methods
##### name
``` solidity
function name() public constant returns (string)
```
Returns the name of the token e.g. `"MyToken"`.
> **returns:** Name of the token
<br/>
##### symbol
``` solidity
function symbol() public constant returns (string)
```
Returns the symbol of the token e.g. `"MYT"`.
> **returns:** Symbol of the token
<br/>
##### granularity
``` solidity
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.
<br/>
##### totalSupply
``` solidity
function totalSupply() public constant returns (uint256)
```
Get the total number of minted tokens.
> **returns:** Total supply of tokens currently in circulation.
<br/>
##### balanceOf
``` solidity
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.
<br/>
##### send
``` solidity
function send(address to, uint256 amount) public
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 a 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 a 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)
<br/>
##### authorizeOperator
``` solidity
function authorizeOperator(address operator) public
```
Authorize a third party `operator` to send tokens on behalf of `msg.sender`.
A `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.
<br/>
##### revokeOperator
``` solidity
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.
<br/>
##### isOperatorFor
``` solidity
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.
<br/>
##### operatorSend
``` solidity
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 a ERC820 lookup on the `from` address
- call the `tokensReceived` method on the contract implementing `ERC777TokensRecipient` as returned by a 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.
<br/>
#### Events
##### Sent
``` solidity
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
<br/>
##### Minted
``` solidity
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 a 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.
<br/>
##### Burned
``` solidity
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 a 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.
<br/>
##### AuthorizedOperator
``` solidity
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.
<br/>
##### RevokedOperator
``` solidity
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.
<br/>
### ERC777TokensSender
Any address (contract or regular account) CAN register a contract (itself or an other) implementing the `ERC777TokensSender` interface via the ERC820 registry.
``` solidity
interface ERC777TokensSender {
function tokensToSend(
address operator,
address from,
address to,
uint value,
bytes userData,
bytes operatorData
) public;
}
```
#### Methods
##### tokensToSend
``` solidity
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 an other) implementing the `ERC777TokensRecipient` interface via the ERC820 registry.
``` solidity
interface ERC777TokensRecipient {
function tokensReceived(address operator, address from, address to, uint amount, bytes userData, bytes operatorData) public;
}
```
#### Methods
##### tokensReceived
``` solidity
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.
### Logo
| **Image** | &nbsp;![beige logo](../assets/eip-777/logo/png/ERC-777-logo-beige-48px.png) | &nbsp;![white logo](../assets/eip-777/logo/png/ERC-777-logo-white-48px.png) | &nbsp;![light grey logo](../assets/eip-777/logo/png/ERC-777-logo-light_grey-48px.png) | &nbsp;![dark grey logo](../assets/eip-777/logo/png/ERC-777-logo-dark_grey-48px.png) | &nbsp;![black logo](../assets/eip-777/logo/png/ERC-777-logo-black-48px.png?raw=true)
|----------:|:---------:|:---------:|:------------:|:-----------:|:---------:|
| **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 pixel. 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](https://github.com/ethereum/EIPs/issues/223) and goes an 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 backwards compatibility with wallets and proxy contracts without having to be redeployed.
## Backwards Compatibility (ERC20Token)
This EIP does not introduce backward incompatibilities and is compatible with the older ERC20 token standard.
This EIP does not uses `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 backwards 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 problem. 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 be register the `ERC20Token` interface 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 accordingly the `ERC20Token` interface via ERC820.
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 a 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 summarize the different actions the token contract must take when sending, minting and transferring token via ERC777 and ERC20:
<table>
<tr>
<th>ERC820</th>
<th><code>to</code> address</th>
<th>ERC777 <code>send</code>/<code>operatorSend</code> and Minting</th>
<th>ERC20 <code>transfer</code></th>
</tr>
<tr>
<td rowspan="2" align="right">
<code>ERC777TokensRecipient</code><br/>registered
</td>
<td>regular address</td>
<td colspan="2" rowspan="2" align="center">
MUST call <code>tokensReceived</code>
</td>
</tr>
<tr>
<td>contract</td>
</tr>
<tr>
<td rowspan="2" align="right">
<code>ERC777TokensRecipient</code><br/>not registered
</td>
<td>regular address</td>
<td align="center">SHOULD accept</td>
<td rowspan="2" align="center">SHOULD accept</td>
</tr>
<tr>
<td>contract</td>
<td align="center">MUST throw</td>
</tr>
</table>
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](https://github.com/jacquesd/ERC777) contains all the [tests](https://github.com/jacquesd/ERC777/blob/master/test/ReferenceToken-test.js).
## Implementation
The repository at [/jacquesd/ERC777](https://github.com/jacquesd/ERC777) contains the [reference implementation](https://github.com/jacquesd/ERC777/blob/master/contracts/ReferenceToken.sol).
## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).