mirror of https://github.com/status-im/EIPs.git
765 lines
43 KiB
Markdown
765 lines
43 KiB
Markdown
---
|
|
eip: 777
|
|
title: A New Advanced Token Standard
|
|
author: Jacques Dafflon <jacques.dafflon@gmail.com>, Jordi Baylina <jordi@baylina.cat>, 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 standard interfaces and behaviors for token contracts.
|
|
|
|
## Abstract
|
|
|
|
This standard defines a new way to interact with a token contract while remaining backward compatible with [ERC20].
|
|
|
|
It defines advanced features to interact with tokens. Namely, *operators* to send tokens on behalf of another address—contract or regular account—and send/receive *hooks* to offer token holders more control over their tokens.
|
|
|
|
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 already-deployed 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 addresses can control and reject which token they send by registering a `tokensToSend` hook. (Rejection is done by `revert`ing in the hook function.)
|
|
3. Both contracts and regular addresses can control and reject which token they receive by registering a `tokensReceived` hook. (Rejection is done by `revert`ing in the hook function.)
|
|
4. The `tokensReceived` hook allows to send tokens to a contract and notify it in a single transaction, unlike [ERC20] which require a double call (`approve`/`transferFrom`) to achieve this.
|
|
5. The token holder can "authorize" and "revoke" operators which 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 `data` bytes field and a similar `operatorData` to be used freely to pass data to the recipient.
|
|
7. It is backward compatible with wallets that do not contain the `tokensReceived` hook function by deploying a proxy contract implementing the `tokensReceived` hook for the wallet.
|
|
|
|
## Specification
|
|
|
|
### ERC777Token (Token Contract)
|
|
|
|
``` solidity
|
|
interface ERC777Token {
|
|
function name() public view returns (string);
|
|
function symbol() public view returns (string);
|
|
function totalSupply() public view returns (uint256);
|
|
function balanceOf(address owner) public view returns (uint256);
|
|
function granularity() public view returns (uint256);
|
|
|
|
function defaultOperators() public view returns (address[]);
|
|
function authorizeOperator(address operator) public;
|
|
function revokeOperator(address operator) public;
|
|
function isOperatorFor(address operator, address tokenHolder) public view returns (bool);
|
|
|
|
function send(address to, uint256 amount, bytes data) public;
|
|
function operatorSend(address from, address to, uint256 amount, bytes data, bytes operatorData) public;
|
|
|
|
function burn(uint256 amount, bytes data) public;
|
|
function operatorBurn(address from, uint256 amount, bytes data, bytes operatorData) public;
|
|
|
|
event Sent(
|
|
address indexed operator,
|
|
address indexed from,
|
|
address indexed to,
|
|
uint256 amount,
|
|
bytes data,
|
|
bytes operatorData
|
|
);
|
|
event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData);
|
|
event Burned(address indexed operator, address indexed from, uint256 amount, bytes operatorData);
|
|
event AuthorizedOperator(address indexed operator, address indexed tokenHolder);
|
|
event RevokedOperator(address indexed operator, address indexed tokenHolder);
|
|
}
|
|
```
|
|
The token contract MUST implement the above interface. The implementation MUST follow the specifications described below.
|
|
|
|
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] functions, every time the switch is triggered, the token MUST register or unregister the `ERC777Token` interface for its own address accordingly via [ERC820]. (Unregistering implies setting the address to `0x0`.)
|
|
|
|
The basic unit token MUST be 10<sup>18</sup> (i.e., [ERC20]'s `decimals` MUST be `18`). All functions MUST consider any amount of tokens (such as balances and amount to send, mint, or burn) in the basic unit.
|
|
|
|
#### **View Functions**
|
|
|
|
The `view` functions detailed below MUST be implemented.
|
|
|
|
**`name` function**
|
|
|
|
``` solidity
|
|
function name() public view returns (string)
|
|
```
|
|
|
|
Returns the name of the token, e.g., `"MyToken"`.
|
|
|
|
> <small>**returns:** Name of the token.</small>
|
|
|
|
**`symbol` function**
|
|
|
|
``` solidity
|
|
function symbol() public view returns (string)
|
|
```
|
|
|
|
Returns the symbol of the token, e.g., `"MYT"`.
|
|
|
|
> <small>**returns:** Symbol of the token.</small>
|
|
|
|
**`totalSupply` function**
|
|
|
|
``` solidity
|
|
function totalSupply() public view returns (uint256)
|
|
```
|
|
|
|
Get the total number of minted tokens.
|
|
|
|
The total supply MUST be equal to the sum of the balances of all addresses—as returned by the `balanceOf` function.
|
|
|
|
The total supply MUST be equal to the sum of all the minted tokens as defined in all the `Minted` events minus the sum of all the burned tokens as defined in all the `Burned` events.
|
|
|
|
> <small>**returns:** Total supply of tokens currently in circulation.</small>
|
|
|
|
**`balanceOf` function**
|
|
|
|
``` solidity
|
|
function balanceOf(address tokenHolder) public view returns (uint256)
|
|
```
|
|
|
|
Get the balance of the account with address `tokenHolder`.
|
|
|
|
The balance MUST be zero (`0`) or higher.
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`tokenHolder`: Address for which the balance is returned.</small>
|
|
>
|
|
> <small>**returns:** Amount of token held by `tokenHolder` in the token contract.</small>
|
|
|
|
**`granularity` function**
|
|
|
|
``` solidity
|
|
function granularity() public view returns (uint256)
|
|
```
|
|
|
|
Get the smallest part of the token that's not divisible.
|
|
|
|
The following rules MUST be applied regarding the *granularity*:
|
|
|
|
- The *granularity* value MUST be at creation time.
|
|
- The *granularity* value MUST NOT be changed ever.
|
|
- The *granularity* value MUST be greater or equal to `1`.
|
|
- Any minting, send or burning of tokens MUST be a multiple of the *granularity* value.
|
|
- Any operation that would result in a balance that's not a multiple of the *granularity* value MUST be considered invalid, and the transaction MUST `revert`.
|
|
|
|
*NOTE*: 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.
|
|
|
|
> <small>**returns:** The smallest non-divisible part of the token.</small>
|
|
|
|
*NOTE*: [`defaultOperators`][defaultOperators] and [`isOperatorFor`][isOperatorFor] are also `view` functions, defined under the [operators] for consistency.
|
|
|
|
*[ERC20] compatibility requirement*:
|
|
The decimals of the token MUST always be `18`. For a *pure* ERC777 token the [ERC20] `decimal` function is OPTIONAL, and its existence SHALL NOT be relied upon when interacting with the token contract. (The decimal value of `18` is implied.) For an [ERC20] enabled token, the `decimal` function is REQUIRED and MUST return `18`.
|
|
|
|
*[ERC20] compatibility requirement*:
|
|
The `name`, `symbol`, `totalSupply`, and `balanceOf` `view` functions MUST be backward compatible with [ERC20].
|
|
|
|
#### **Operators**
|
|
|
|
An `operator` is an address which is allowed to send and burn tokens on behalf of another address.
|
|
|
|
When an address becomes an *operator* for a *token holder*, an `AuthorizedOperator` event MUST be emitted. The `AuthorizedOperator`'s `operator` (topic 1) and `tokenHolder` (topic 2) MUST be the addresses of the *operator* and the *token holder* respectively.
|
|
|
|
When a *token holder* revokes an *operator*, a `RevokedOperator` event MUST be emitted. The `RevokedOperator`'s `operator` (topic 1) and `tokenHolder` (topic 2) MUST be the addresses of the *operator* and the *token holder* respectively.
|
|
|
|
*NOTE*: A *token holder* MAY have multiple *operators* at the same time.
|
|
|
|
The token MAY define *default operators*. A *default operator* is an implicitly authorized *operator* for all *token holders*. `AuthorizedOperator` events MUST NOT be emitted when defining the *default operators*. The rules below apply to *default operators*:
|
|
|
|
- The token contract MUST define *default operators* at creation time.
|
|
- The *default operators* MUST be invariants. I.e., the token contract MUST NOT add or remove *default operators* ever.
|
|
- `AuthorizedOperator` events MUST NOT be emitted when defining *default operators*.
|
|
- A *token holder* MUST be allowed revoke a *default operator* (unless the *token holder* is the *default operator* in question).
|
|
- A *token holder* MUST be allowed to re-authorize a previously revoked *default operator*.
|
|
- When a *default operator* is explicitly authorized or revoked for a specific *token holder*, an `AuthorizedOperator` or `RevokedOperator` event (respectively) MUST be emitted.
|
|
|
|
The following rules apply to any *operator*:
|
|
|
|
- An address MUST always be an *operator* for itself. Hence an address MUST NOT ever be revoked as its own *operator*.
|
|
- If an address is an *operator* for a *token holder*, `isOperatorFor` MUST return `true`.
|
|
- If an address is not an *operator* for a *token holder*, `isOperatorFor` MUST return `false`.
|
|
- The token contract MUST emit an `AuthorizedOperator` event with the correct values when a *token holder* authorizes an address as its *operator* as defined in the [`AuthorizedOperator` Event][authorizedoperator].
|
|
- The token contract MUST emit a `RevokedOperator` event with the correct values when a *token holder* revokes an address as its *operator* as defined in the [`RevokedOperator` Event][revokedoperator].
|
|
|
|
*NOTE*: A *token holder* MAY authorize an already authorized *operator*. An `AuthorizedOperator` MUST be emitted each time.
|
|
|
|
*NOTE*: A *token holder* MAY revoke an already revoked *operator*. A `RevokedOperator` MUST be emitted each time.
|
|
|
|
*NOTE*: A token holder MAY have multiple *operators* at the same time.
|
|
|
|
**`AuthorizedOperator` event** <a id="authorizedoperator"></a>
|
|
|
|
``` solidity
|
|
event AuthorizedOperator(address indexed operator, address indexed tokenHolder)
|
|
```
|
|
|
|
Indicates the authorization of `operator` as an *operator* for `tokenHolder`.
|
|
|
|
*NOTE*: This event MUST NOT be emitted outside of an *operator* authorization process.
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`operator`: Address which became an *operator* of `tokenHolder`.</small>
|
|
> <small>`tokenHolder`: Address of a token holder which authorized the `operator` address as an *operator*.</small>
|
|
|
|
|
|
**`RevokedOperator` event** <a id="revokedoperator"></a>
|
|
|
|
``` solidity
|
|
event RevokedOperator(address indexed operator, address indexed tokenHolder)
|
|
```
|
|
|
|
Indicates the revocation of `operator` as an *operator* for `tokenHolder`.
|
|
|
|
*NOTE*: This event MUST NOT be emitted outside of an *operator* revocation process.
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`operator`: Address which was revoked as an *operator* of `tokenHolder`.</small>
|
|
> <small>`tokenHolder`: Address of a token holder which revoked the `operator` address as an *operator*.</small>
|
|
|
|
The `defaultOperators`, `authorizeOperator`, `revokeOperator` and `isOperatorFor` functions described below MUST be implemented to manage *operators*.
|
|
Token contracts MAY implement other functions to manage *operators*.
|
|
|
|
**`defaultOperators` function** <a id="defaultOperators"></a>
|
|
|
|
``` solidity
|
|
function defaultOperators() public view returns (address[])
|
|
```
|
|
|
|
Get the list of *default operators* as defined by the token contract.
|
|
|
|
*NOTE*: If the token contract does not have any *default operators*, this function MUST return an empty list.
|
|
|
|
> <small>**returns:** List of addresses of all the *default operators*.
|
|
|
|
**`authorizeOperator` function**
|
|
|
|
``` solidity
|
|
function authorizeOperator(address operator) public
|
|
```
|
|
|
|
Set a third party `operator` address as an *operator* of `msg.sender` to send and burn tokens on its behalf.
|
|
|
|
*NOTE*: The *token holder* (`msg.sender`) is always an *operator* for itself. This right SHALL NOT be revoked. Hence this function MUST `revert` if it is called to authorize the token holder (`msg.sender`) as an *operator* for itself (i.e. if `operator` is equal to `msg.sender`).
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`operator`: Address to set as an *operator* for `msg.sender`.</small>
|
|
|
|
**`revokeOperator` function**
|
|
|
|
``` solidity
|
|
function revokeOperator(address operator) public
|
|
```
|
|
|
|
Remove the right of the `operator` address to be an *operator* for `msg.sender` and to send and burn tokens on its behalf.
|
|
|
|
*NOTE*: The *token holder* (`msg.sender`) is always an *operator* for itself. This right SHALL NOT be revoked. Hence this function MUST `revert` if it is called to revoke the token holder (`msg.sender`) as an *operator* for itself (i.e., if `operator` is equal to `msg.sender`).
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`operator`: Address to rescind as an *operator* for `msg.sender`.</small>
|
|
|
|
**`isOperatorFor` function** <a id="isOperatorFor"></a>
|
|
|
|
``` solidity
|
|
function isOperatorFor(address operator, address tokenHolder) public view returns (bool)
|
|
```
|
|
|
|
Indicate whether the `operator` address is an *operator* of the `tokenHolder` address.
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`operator`: Address which may be an *operator* of `tokenHolder`.</small>
|
|
> <small>`tokenHolder`: Address of a token holder which may have the `operator` address as an *operator*.</small>
|
|
>
|
|
> <small>**returns:** `true` if `operator` is an *operator* of `tokenHolder` and `false` otherwise.
|
|
|
|
*NOTE*: To know which addresses are *operators* for a given *token holder*, one MUST call `isOperatorFor` with the *token holder* for each *default operator* and parse the `AuthorizedOperator`, and `RevokedOperator` events for the *token holder* in question.
|
|
|
|
#### **Sending Tokens**
|
|
|
|
When an *operator* sends an `amount` of tokens from a *token holder* to a *recipient* with the associated `data` and `operatorData`, the token contract MUST apply the following rules:
|
|
|
|
- Any *token holder* MAY send tokens to any *recipient*.
|
|
- The balance of the *token holder* MUST be decreased by the `amount`.
|
|
- The balance of the *recipient* MUST be increased by the `amount`.
|
|
- The balance of the *token holder* MUST be greater or equal to the `amount`—such that its resulting balance is greater or equal to zero (`0`) after the send.
|
|
- The token contract MUST emit a `Sent` event with the correct values as defined in the [`Sent` Event][sent].
|
|
- The *operator* MAY communicate any information in the `operatorData`.
|
|
- The token contract MUST call the `tokensToSend` hook of the *token holder* if the *token holder* registers an `ERC777TokensRecipient` implementation via [ERC820].
|
|
- The token contract MUST call the `tokensReceived` hook of the *recipient* if the *recipient* registers an `ERC777TokensRecipient` implementation via [ERC820].
|
|
- The `data` and `operatorData` MUST be immutable during the entire send process—hence the same `data` and `operatorData` MUST be used to call both hooks and emit the `Sent` event.
|
|
|
|
The token contract MUST `revert` when sending in any of the following cases:
|
|
|
|
- The *operator* address is not an authorized operator for the *token holder*.
|
|
- The resulting *token holder* balance or *recipient* balance after the send is not a multiple of the *granularity* defined by the token contract.
|
|
- The address of the *token holder* or the *recipient* is `0x0`.
|
|
- Any of the resulting balances becomes negative, i.e. becomes less than zero (`0`).
|
|
|
|
The token contract MAY send tokens from many *token holders*, to many *recipients*, or both. In this case:
|
|
|
|
- The previous send rules MUST apply to all the *token holders* and all the *recipients*.
|
|
- The sum of all the balances incremented MUST be equal to the total sent `amount`.
|
|
- The sum of all the balances decremented MUST be equal to the total sent `amount`.
|
|
- A `Sent` event MUST be emitted for every *token holder* and *recipient* pair with the corresponding amount for each pair.
|
|
- The sum of all the amounts from the `Sent` event MUST be equal to the total sent `amount`.
|
|
|
|
*NOTE*: Mechanisms such as applying a fee on a send is considered as a send to multiple *recipients*: the intended *recipient* and the fee *recipient*.
|
|
|
|
*NOTE*: Transfer of tokens MAY be chained. For example, if a contract upon receiving tokens sends them further to another address. In this case, the previous send rules apply to each send, in order.
|
|
|
|
*NOTE*: Sending an amount of zero (`0`) tokens is valid and MUST be treated as a regular send.
|
|
|
|
*Implementation Requirement*:
|
|
- The token contract MUST call the `tokensToSend` hook *before* updating the state.
|
|
- The token contract MUST call the `tokensReceived` hook *after* updating the state.
|
|
I.e., `tokensToSend` MUST be called first, then the balances MUST be updated to reflect the send, and finally `tokensReceived` MUST be called *afterward*. Thus a `balanceOf` call within `tokensToSend` returns the balance of the address *before* the send and a `balanceOf` call within `tokensReceived` returns the balance of the address *after* the send.
|
|
|
|
*NOTE*: The `data` field contains extra information intended for, and defined by the recipient— similar to the data field in a regular ether send transaction. Typically, `data` is used to describe the intent behind the send. The `operatorData` MUST only be provided by the *operator*. It is intended more for logging purposes and particular cases. (Examples include payment references, cheque numbers, countersignatures and more.) In most of the cases the recipient would ignore the `operatorData`, or at most, it would log the `operatorData`.
|
|
|
|
**`Sent` event** <a id="sent"></a>
|
|
|
|
``` solidity
|
|
event Sent(address indexed operator, address indexed from, address indexed to, uint256 amount, bytes data, bytes operatorData)
|
|
```
|
|
|
|
Indicate a send of `amount` of tokens from the `from` address to the `to` address by the `operator` address.
|
|
|
|
*NOTE*: This event MUST NOT be emitted outside of a send or an [ERC20] transfer process.
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`operator`: Address which triggered the send.</small>
|
|
> <small>`from`: Token holder.</small>
|
|
> <small>`to`: Token recipient.</small>
|
|
> <small>`amount`: Number of tokens to send.</small>
|
|
> <small>`data`: Information attached to the send, and intended for the recipient (`to`).</small>
|
|
> <small>`operatorData`: Information attached to the send by the `operator`.</small>
|
|
|
|
The `send` and `operatorSend` functions described below MUST be implemented to send tokens.
|
|
Token contracts MAY implement other functions to send tokens.
|
|
|
|
*NOTE*: An address MAY send an amount of `0`, which is valid and MUST be treated as a regular send.
|
|
|
|
**`send` function**
|
|
|
|
``` solidity
|
|
function send(address to, uint256 amount, bytes data) public
|
|
```
|
|
|
|
Send the `amount` of tokens from the address `msg.sender` to the address `to`.
|
|
|
|
The *operator* and the *token holder* MUST both be the `msg.sender`.
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`to`: Token recipient.</small>
|
|
> <small>`amount`: Number of tokens to send.</small>
|
|
> <small>`data`: Information attached to the send, and intended for the recipient (`to`).</small>
|
|
|
|
**`operatorSend` function**
|
|
|
|
``` solidity
|
|
function operatorSend(address from, address to, uint256 amount, bytes data, bytes operatorData) public
|
|
```
|
|
|
|
Send the `amount` of tokens on behalf of the address `from` to the address `to`.
|
|
|
|
The *operator* MUST be `msg.sender`. The value of `from` MAY be `0x0`, then the `from` (*token holder*) used for the send MUST be `msg.sender` (the `operator`).
|
|
|
|
*Reminder*: If the *operator* address is not an authorized operator of the `from` address, then the send process MUST `revert`.
|
|
|
|
*NOTE*: `from` and `msg.sender` MAY be the same address. I.e., an address MAY call `operatorSend` for itself. This call MUST be equivalent to `send` with the addition that the *operator* MAY specify an explicit value for `operatorData` (which cannot be done with the `send` function).
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`from`: Token holder (or `0x0` to set `from` to `msg.sender`).</small>
|
|
> <small>`to`: Token recipient.</small>
|
|
> <small>`amount`: Number of tokens to send.</small>
|
|
> <small>`data`: Information attached to the send, and intended for the recipient (`to`).</small>
|
|
> <small>`operatorData`: Information attached to the send by the `operator`.</small>
|
|
|
|
#### **Minting Tokens**
|
|
|
|
Minting tokens is the act of producing new tokens. [ERC777] intentionally does not define specific functions to mint tokens. This intent comes from the wish not to limit the use of the [ERC777] standard as the minting process is generally specific for every token.
|
|
|
|
Nonetheless, the rules below MUST be respected when minting for a *recipient*:
|
|
|
|
- Tokens MAY be minted for any *recipient* address.
|
|
- The total supply MUST be increased by the amount of tokens minted.
|
|
- The balance of `0x0` MUST NOT be decreased.
|
|
- The balance of the *recipient* MUST be increased by the amount of tokens minted.
|
|
- The token contract MUST emit a `Minted` event with the correct values as defined in the [`Minted` Event][minted].
|
|
- The token contract MUST call the `tokensReceived` hook of the *recipient* if the *recipient* registers an `ERC777TokensRecipient` implementation via [ERC820].
|
|
- The `data` and `operatorData` MUST be immutable during the entire mint process—hence the same `data` and `operatorData` MUST be used to call the `tokensReceived` hook and emit the `Minted` event.
|
|
|
|
The token contract MUST `revert` when minting in any of the following cases:
|
|
|
|
- The resulting *recipient* balance after the mint is not a multiple of the *granularity* defined by the token contract.
|
|
- The *recipient* is a contract, and it does not implement the `ERC777TokensRecipient` interface via [ERC820].
|
|
- The address of the *recipient* is `0x0`.
|
|
|
|
*[ERC20] compatibility requirement*:
|
|
While a `Sent` event MUST NOT be emitted when minting, if the token contract is [ERC20] backward compatible, a `Transfer` event with the `from` parameter set to `0x0` SHOULD be emitted as defined in the [ERC20] standard.
|
|
|
|
The token contract MAY mint tokens for multiple *recipients* at once. In this case:
|
|
|
|
- The previous mint rules MUST apply to all the *recipients*.
|
|
- The sum of all the balances incremented MUST be equal to the total minted amount.
|
|
- A `Minted` event MUST be emitted for every *recipient* with the corresponding amount for each *recipient*.
|
|
- The sum of all the amounts from the `Minted` event MUST be equal to the total minted `amount`.
|
|
|
|
*NOTE*: Minting an amount of zero (`0`) tokens is valid and MUST be treated as a regular mint.
|
|
|
|
*NOTE*: The `data` field contains extra information intended for, and defined by the recipient— similar to the data field in a regular ether send transaction. Typically, `data` is used to describe the intent behind the mint. The `operatorData` MUST only be provided by the *operator*. It is intended more for logging purposes and particular cases. (Examples include payment references, cheque numbers, countersignatures and more.) In most of the cases the recipient would ignore the `operatorData`, or at most, it would log the `operatorData`.
|
|
|
|
**`Minted` event** <a id="minted"></a>
|
|
|
|
``` solidity
|
|
event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData)
|
|
```
|
|
|
|
Indicate the minting of `amount` of tokens to the `to` address by the `operator` address.
|
|
|
|
*NOTE*: This event MUST NOT be emitted outside of a mint process.
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`operator`: Address which triggered the mint.</small>
|
|
> <small>`to`: Token recipient.</small>
|
|
> <small>`amount`: Number of tokens minted.</small>
|
|
> <small>`data`: Information attached to the minting, and intended for the recipient (`to`).</small>
|
|
> <small>`operatorData`: Information attached to the minting by the `operator`.</small>
|
|
|
|
#### **Burning Tokens**
|
|
|
|
Burning tokens is the act of destroying existing tokens. [ERC777] explicitly defines two functions to burn tokens (`burn` and `operatorBurn`). These functions facilitate the integration of the burning process in wallets and dapps. However, the token contract MAY prevent some or all *token holders* from burning tokens for any reason. The token contract MAY also define other functions to burn tokens.
|
|
|
|
The rules below MUST be respected when burning the tokens of a *token holder*:
|
|
|
|
- Tokens MAY be burned from any *token holder* address.
|
|
- The total supply MUST be decreased by the amount of tokens burned.
|
|
- The balance of `0x0` MUST NOT be increased.
|
|
- The balance of the *token holder* MUST be decreased by amount of tokens burned.
|
|
- The token contract MUST emit a `Burned` event with the correct values as defined in the [`Burned` Event][burned].
|
|
- The token contract MUST call the `tokensToSend` hook of the *token holder* if the *token holder* registers an `ERC777TokensSender` implementation via [ERC820].
|
|
- The `operatorData` MUST be immutable during the entire burn process—hence the same `operatorData` MUST be used to call the `tokensToSend` hook and emit the `Burned` event.
|
|
- The `data` field of the `tokensToSend` hook MUST be empty.
|
|
|
|
The token contract MUST `revert` when burning in any of the following cases:
|
|
|
|
- The *operator* address is not an authorized operator for the *token holder*.
|
|
- The resulting *token holder* balance after the burn is not a multiple of the *granularity* defined by the token contract.
|
|
- The balance of *token holder* is inferior to the amount of tokens to burn (i.e., resulting in a negative balance for the *token holder*).
|
|
- The address of the *token holder* is `0x0`.
|
|
|
|
*[ERC20] compatibility requirement*:
|
|
While a `Sent` event MUST NOT be emitted when burning; if the token contract is [ERC20] enabled, a `Transfer` event with the `to` parameter set to `0x0` SHOULD be emitted. The [ERC20] standard does not define the concept of burning tokens, but this is a commonly accepted practice.
|
|
|
|
The token contract MAY burn tokens for multiple *token holders* at once. In this case:
|
|
|
|
- The previous burn rules MUST apply to each *token holders*.
|
|
- The sum of all the balances decremented MUST be equal to the total burned amount.
|
|
- A `Burned` event MUST be emitted for every *token holder* with the corresponding amount for each *token holder*.
|
|
- The sum of all the amounts from the `Burned` event MUST be equal to the total burned `amount`.
|
|
|
|
*NOTE*: Burning an amount of zero (`0`) tokens is valid and MUST be treated as a regular burn.
|
|
|
|
**`Burned` event** <a id="burned"></a>
|
|
|
|
``` solidity
|
|
event Burned(address indexed operator, address indexed from, uint256 amount, bytes operatorData)
|
|
```
|
|
|
|
Indicate the burning of `amount` of tokens from the `from` address by the `operator` address.
|
|
|
|
*NOTE*: This event MUST NOT be emitted outside of a burn process.
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`operator`: Address which triggered the burn.</small>
|
|
> <small>`from`: Token holder whose tokens are burned.</small>
|
|
> <small>`amount`: Number of tokens burned.</small>
|
|
> <small>`operatorData`: Information attached to the burn by the `operator`.</small>
|
|
|
|
The `burn` and `operatorBurn` functions described below MUST be implemented to burn tokens.
|
|
Token contracts MAY implement other functions to burn tokens.
|
|
|
|
**`burn` function**
|
|
|
|
``` solidity
|
|
function burn(uint256 amount) public;
|
|
```
|
|
|
|
Burn the `amount` of tokens from the address `msg.sender`.
|
|
|
|
The *operator* and the *token holder* MUST both be the `msg.sender`.
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`amount`: Number of tokens to burn.</small>
|
|
|
|
**`operatorBurn` function**
|
|
|
|
``` solidity
|
|
function operatorBurn(address from, uint256 amount, bytes operatorData) public;
|
|
```
|
|
|
|
Burn the `amount` of tokens on behalf of the address `from`.
|
|
|
|
The *operator* MUST be `msg.sender`. The value of `from` MAY be `0x0`, then the `from` (*token holder*) used for the burn MUST be `msg.sender` (the `operator`).
|
|
|
|
*Reminder*: If the *operator* address is not an authorized operator of the `from` address, then the burn process MUST `revert`.
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`from`: Token holder whose tokens will be burned (or `0x0` to set `from` to `msg.sender`).</small>
|
|
> <small>`amount`: Number of tokens to burn.</small>
|
|
> <small>`operatorData`: Information attached to the burn by the *operator*.</small>
|
|
|
|
*NOTE*: The *operator* MAY pass any information via `operatorData`. The `operatorData` MUST only be provided by the *operator*.
|
|
|
|
*NOTE*: `from` and `msg.sender` MAY be the same address. I.e., an address MAY call `operatorBurn` for itself. This call MUST be equivalent to `burn` with the addition that the *operator* MAY specify an explicit value for `operatorData` (which cannot be done with the `burn` function).
|
|
|
|
#### **`ERC777TokensSender` And The `tokensToSend` Hook**
|
|
|
|
The `tokensToSend` hook notifies of any decrement of balance (send and burn) for a given *token holder*. Any address (regular or contract) wishing to be notified of token debits from their address MAY register the address of a contract implementing the `ERC777TokensSender` interface described below via [ERC820].
|
|
|
|
``` solidity
|
|
interface ERC777TokensSender {
|
|
function tokensToSend(
|
|
address operator,
|
|
address from,
|
|
address to,
|
|
uint256 amount,
|
|
bytes data,
|
|
bytes operatorData
|
|
) public;
|
|
}
|
|
```
|
|
|
|
*NOTE*: A regular address MAY register a different address—the address of a contract—implementing the interface on its behalf. A contract MAY register either its address or the address of another contract but said address MUST implement the interface on its behalf.
|
|
|
|
**`tokensToSend`**
|
|
|
|
``` solidity
|
|
function tokensToSend(address operator, address from, address to, uint256 amount, bytes data, bytes operatorData) public
|
|
```
|
|
|
|
Notify a send or burn (if `to` is `0x0`) of `amount` tokens from the `from` address to the `to` address by the `operator` address.
|
|
|
|
*NOTE*: This function MUST NOT be called outside of a burn, send or [ERC20] transfer process.
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`operator`: Address which triggered the balance decrease (through sending or burning).</small>
|
|
> <small>`from`: *token holder*.</small>
|
|
> <small>`to`: *token recipient* for a send and `0x` for a burn.</small>
|
|
> <small>`amount`: Number of tokens the *token holder* balance is decreased by.</small>
|
|
> <small>`data`: Extra information provided by the *token holder*.</small>
|
|
> <small>`operatorData`: Extra information provided by the address which triggered the balance decrease.</small>
|
|
|
|
The following rules apply when calling the `tokensToSend` hook:
|
|
|
|
- The `tokensToSend` hook MUST be called every time the balance is decremented.
|
|
- The `tokensToSend` hook MUST be called *before* the state is updated—i.e. *before* the balance is decremented.
|
|
- `operator` MUST be the address which triggered the decrease of the balance.
|
|
- `from` MUST be the address of the *token holder* whose balance is decreased.
|
|
- `to` MUST be the address of the *recipient* whose balance is increased for a send.
|
|
- `to` MUST be `0x0` for a burn.
|
|
- `amount` MUST be the number of tokens the *token holder* balance is decreased by.
|
|
- `data` MUST contain the extra information provided by the *token holder* (if any) for a send.
|
|
- `data` MUST be empty for a burn.
|
|
- `operatorData` MUST contain the extra information provided by the address which triggered the decrease of the balance (if any).
|
|
- The *token holder* MAY block a decrease of its balance by `revert`ing. (I.e., reject the withdrawal of tokens from its account.)
|
|
|
|
*NOTE*: Multiple *token holders* MAY use the same implementation of `ERC777TokensSender`.
|
|
|
|
*NOTE*: An address can register at most one implementation at any given time for all [ERC777] tokens. Hence the `ERC777TokensSender` MUST expect to be called by different token contracts. The `msg.sender` of the `tokensToSend` call is expected to be the address of the token contract.
|
|
|
|
*[ERC20] compatibility requirement*:
|
|
This hook takes precedence over [ERC20] and MUST be called (if registered) when calling [ERC20]'s `transfer` and `transferFrom` event. When called from a `transfer`, `operator` MUST be the same value as the `from`. When called from a `transferFrom`, `operator` MUST be the address which issued the `transferFrom` call.
|
|
|
|
#### **`ERC777TokensRecipient` And The `tokensReceived` Hook**
|
|
|
|
The `tokensReceived` hook notifies of any increment of the balance (send and mint) for a given *recipient*. Any address (regular or contract) wishing to be notified of token credits to their address MAY register the address of a contract implementing the `ERC777TokensSender` interface described below via [ERC820].
|
|
|
|
``` solidity
|
|
interface ERC777TokensRecipient {
|
|
function tokensReceived(
|
|
address operator,
|
|
address from,
|
|
address to,
|
|
uint256 amount,
|
|
bytes data,
|
|
bytes operatorData
|
|
) public;
|
|
}
|
|
```
|
|
|
|
If the *recipient* is a contract, which has not registered an `ERC777TokensRecipient` implementation; the token contract:
|
|
|
|
- MUST `revert` if the `tokensReceived` hook is called from a mint or send call.
|
|
- SHOULD accept if the `tokensReceived` hook is called from an ERC20 `transfer` or `transferFrom` call.
|
|
|
|
*NOTE*: A regular address MAY register a different address—the address of a contract—implementing the interface on its behalf. A contract MUST register either its address or the address of another contract but said address MUST implement the interface on its behalf.
|
|
|
|
**`tokensReceived`**
|
|
|
|
``` solidity
|
|
function tokensReceived(address operator, address from, address to, uint256 amount, bytes data, bytes operatorData) public
|
|
```
|
|
|
|
Notify a send or mint (if `from` is `0x0`) of `amount` tokens from the `from` address to the `to` address by the `operator` address.
|
|
|
|
*NOTE*: This function MUST NOT be called outside of a mint, send or [ERC20] transfer process.
|
|
|
|
> <small>**parameters**</small>
|
|
> <small>`operator`: Address which triggered the balance increase (through sending or minting).</small>
|
|
> <small>`from`: *token holder* for a send and `0x` for a mint.</small>
|
|
> <small>`to`: *token recipient*.</small>
|
|
> <small>`amount`: Number of tokens the *recipient* balance is increased by.</small>
|
|
> <small>`data`: Extra information provided by the *token holder* for a send and nothing (empty bytes) for a mint,.</small>
|
|
> <small>`operatorData`: Extra information provided by the address which triggered the balance increase.</small>
|
|
|
|
The following rules apply when calling the `tokensToSend` hook:
|
|
|
|
- The `tokensReceived` hook MUST be called every time the balance is incremented.
|
|
- The `tokensReceived` hook MUST be called *after* the state is updated—i.e. *after* the balance is incremented.
|
|
- `operator` MUST be the address which triggered the increase of the balance.
|
|
- `from` MUST be the address of the *token holder* whose balance is decreased for a send.
|
|
- `from` MUST be `0x0` for a mint.
|
|
- `to` MUST be the address of the *recipient* whose balance is increased.
|
|
- `amount` MUST be the number of tokens the *recipient* balance is increased by.
|
|
|
|
- `operatorData` MUST contain the extra information provided by the address which triggered the increase of the balance (if any).
|
|
- The *token holder* MAY block an increase of its balance by `revert`ing. (I.e., reject the reception of tokens.)
|
|
|
|
*NOTE*: Multiple *token holders* MAY use the same implementation of `ERC777TokensRecipient`.
|
|
|
|
*NOTE*: An address can register at most one implementation at any given time for all [ERC777] tokens. Hence the `ERC777TokensRecipient` MUST expect to be called by different token contracts. The `msg.sender` of the `tokensReceived` call is expected to be the address of the token contract.
|
|
|
|
*[ERC20] compatibility requirement*:
|
|
This hook takes precedence over [ERC20] and MUST be called (if registered) when calling [ERC20]'s `transfer` and `transferFrom` event. When called from a `transfer`, `operator` MUST be the same value as the `from`. When called from a `transferFrom`, `operator` MUST be the address which issued the `transferFrom` call.
|
|
|
|
#### **Note On Gas Consumption**
|
|
Dapps and wallets SHOULD first estimate the gas required when sending, minting, or burning tokens—using [`eth_estimateGas`][eth_estimateGas]—to avoid running out of gas during the transaction.
|
|
|
|
### Logo
|
|
|
|
| **Image** | ![beige logo] | ![white logo] | ![light grey logo] | ![dark grey logo] | ![black logo][black logo] |
|
|
|----------:|:---------:|:---------:|:------------:|:-----------:|:---------:|
|
|
| **Color** | beige | white | light grey | dark grey | black |
|
|
| **Hex** | `#C99D66` | `#FFFFFF` | `#EBEFF0` | `#3C3C3D` | `#000000` |
|
|
|
|
The logo MAY be used, modified and adapted to promote valid [ERC777] token implementations and [ERC777] compliant technologies such as wallets and dapps.
|
|
|
|
[ERC777] token contract authors MAY create a specific logo for their token based on this logo.
|
|
|
|
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`][logos] folder in `SVG` and `PNG` formats. The `PNG` version of the logo offers a few sizes in pixels. If needed, other sizes MAY be created by converting from `SVG` into `PNG`.
|
|
|
|
## Rationale
|
|
|
|
This standard solves some of the shortcomings of [ERC20] while maintaining backward compatibility with [ERC20]. It avoids the problems and vulnerabilities of [EIP223].
|
|
|
|
It goes a step further by allowing *operators* (generally contracts) which can manage the tokens in the same way that the [ERC20] with infinite `approve` was allowed. Finally, it adds hooks to provide further control to *token holders* over their tokens. Note that, the usage of [ERC820] provides backward compatibility with wallets and existing contracts without having to be redeployed thanks proxy contracts implementing the hooks.
|
|
|
|
## Backward Compatibility
|
|
|
|
This EIP does not introduce backward incompatibilities and is backward compatible with the older [ERC20] token standard.
|
|
|
|
This EIP does not use `transfer` and `transferFrom` and uses `send` and `operatorSend` to avoid confusion and mistakes when deciphering which token standard is being used.
|
|
|
|
This standard allows the implementation of [ERC20] functions `transfer`, `transferFrom`, `approve` and `allowance` alongside to make a token fully compatible with [ERC20].
|
|
|
|
The token MAY implement `decimals()` for backward compatibility with [ERC20]. If implemented, it MUST always return `18`.
|
|
|
|
Therefore a token contract MAY implement both [ERC20] and [ERC777] in parallel. The specification of the `view` 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 state-modifying functions 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] functions, 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 difference for new contracts implementing [ERC20] is that `tokensToSend` and `tokensReceived` hooks take precedence over [ERC20]. Even with an [ERC20] `transfer` and `transferFrom` call, the token contract MUST check via [ERC820] if the `from` and the `to` address implement `tokensToSend` and `tokensReceived` hook respectively. If any hook is implemented, it MUST be called. 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]:
|
|
|
|
|
|
<table>
|
|
<tr>
|
|
<th align="right">ERC820</th>
|
|
<th><code>to</code> address</th>
|
|
<th align="center">ERC777 Sending And Minting</th>
|
|
<th align="center">ERC20 <code>transfer</code>/<code>transferFrom</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 <code>revert</code></td>
|
|
</tr>
|
|
</table>
|
|
|
|
There is no particular action to take if `tokensToSend` is not implemented. The transfer MUST proceed and only be canceled if another condition is not respected such as lack of funds or a `revert` in `tokensReceived` (if present).
|
|
|
|
During a send, mint and burn, the respective `Sent`, `Minted` and `Burned` events MUST be emitted. Furthermore, if the token contract declares that it implements `ERC20Token` via [ERC820], the token contract SHOULD emit a `Transfer` event for minting and burning and MUST emit a `Transfer` event for sending (as specified in the [ERC20] standard). During an [ERC20]'s `transfer` or `transferFrom` functions, a valid `Sent` event MUST be emitted.
|
|
|
|
Hence for any movement of tokens, two events MAY be emitted: an [ERC20] `Transfer` and an [ERC777] `Sent`, `Minted` or `Burned` (depending on the type of movement). Third-party developers MUST be careful not to consider both events as separate movements. As a general rule, if an application considers the token as an ERC20 token, then only the `Transfer` event MUST be taken into account. If the application considers the token as an ERC777 token, then only the `Sent`, `Minted` and `Burned` events MUST be considered.
|
|
|
|
## Test Cases
|
|
|
|
The [repository with the reference implementation][jacquesd/ERC777] contains all the [tests][ref tests].
|
|
|
|
## Implementation
|
|
|
|
The GitHub repository [jacquesd/ERC777] contains the [reference implementation]. The reference implementation is also available via [npm][npm/erc777] and can be installed with `npm install erc777`.
|
|
|
|
|
|
## Copyright
|
|
|
|
Copyright and related rights waived via [CC0].
|
|
|
|
[operators]: #operators
|
|
|
|
|
|
[ERC20]: https://eips.ethereum.org/EIPS/eip-20
|
|
[ERC777]: https://eips.ethereum.org/EIPS/eip-777
|
|
[ERC820]: https://eips.ethereum.org/EIPS/eip-820
|
|
[jacquesd/ERC777]: https://github.com/jacquesd/ERC777
|
|
[npm/erc777]: https://www.npmjs.com/package/erc777
|
|
[ref tests]: https://github.com/jacquesd/ERC777/blob/master/test/ReferenceToken.test.js
|
|
[reference implementation]: https://github.com/jacquesd/ERC777/blob/master/contracts/examples/ReferenceToken.sol
|
|
[EIP223]: https://github.com/ethereum/EIPs/issues/223
|
|
[eth_estimateGas]: https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_estimategas
|
|
|
|
[authorizedoperator]: #authorizedoperator
|
|
[revokedoperator]: #revokedoperator
|
|
[isOperatorFor]: #isOperatorFor
|
|
[defaultOperators]: #defaultOperators
|
|
[sent]: #sent
|
|
[minted]: #minted
|
|
[burned]: #burned
|
|
|
|
[logos]: https://github.com/ethereum/EIPs/tree/master/assets/eip-777/logo
|
|
[beige logo]: ../assets/eip-777/logo/png/ERC-777-logo-beige-48px.png
|
|
[white logo]: ../assets/eip-777/logo/png/ERC-777-logo-white-48px.png
|
|
[light grey logo]: ../assets/eip-777/logo/png/ERC-777-logo-light_grey-48px.png
|
|
[dark grey logo]: ../assets/eip-777/logo/png/ERC-777-logo-dark_grey-48px.png
|
|
[black logo]: ../assets/eip-777/logo/png/ERC-777-logo-black-48px.png
|
|
|
|
[CC0]: https://creativecommons.org/publicdomain/zero/1.0/
|