23 KiB
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 for this standard containing the reference implementation 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:
- Uses the same philosophy as Ether in that tokens are sent with
send(dest, value, data)
. - 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. - 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. - The
tokensReceived
function also avoids the double call needed in the ERC20 standard (approve
/transferFrom
). - 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.
- Every token transaction contains a
userData
bytes field and a similaroperatorData
– 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. - 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)
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 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 returnedreturns: Amount of token held by
tokenHolder
in the token contract.
send
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 implementingERC777TokensSender
as returned by a ERC820 lookup on thefrom
address – regardless iffrom
is a regular address or a contract. - call the
tokensReceived
method on the address implementingERC777TokensRecipient
as returned by a ERC820 lookup on theto
address – regardless ifto
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 spendto
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 recipientamount
: number of tokens transferreduserData
: 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
.
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.
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 implementingERC777TokensSender
as returned by a ERC820 lookup on thefrom
address - call the
tokensReceived
method on the contract implementingERC777TokensRecipient
as returned by a ERC820 lookup on theto
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 theERC777TokensRecipient
interface.to
is a contract which is not prepared to receive tokens.msg.sender
is not an authorized operator forfrom
.
parameters
from
: token holder (sender)to
: tokens recipientamount
: number of tokens transferreduserData
: 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 foroperatorSend
from
: token holderto
: tokens recipientamount
: number of tokens sentuserData
: information attached to the transaction by the token holderoperatorData
: 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 mintingto
: tokens recipientamount
: number of tokens mintedoperatorData
: 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.
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 mintingfrom
: token holderamount
: number of tokens burneduserData
: information attached to the burn by the token holderoperatorData
: 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.
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 an other) 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 foroperatorSend
from
: token holder (sender)to
: tokens recipient (or0x
for burning)amount
: number of tokens sent or burneduserData
: information attached to the transaction by the senderoperatorData
: information attached to the transaction by the operator
Burning Versus Sending
When tokens are sent as a result of burning:
to
MUST be0x0
userData
MUST be emptyoperator
MUST be the address which initiated the burningoperatorData
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 sentto
MUST NOT be0x0
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.
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 foroperatorSend
from
: token holder (sender or0x
for minting)to
: tokens recipient (or0x
for burning)amount
: number of tokens sent, minted or burneduserData
: information attached to the transaction by the senderoperatorData
: information attached to the transaction by the operator
Minting Versus Sending
When tokens are received as a result of minting:
from
MUST be0x0
userData
MUST be emptyoperator
MUST be the address which initiated the mintingoperatorData
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 andfrom
MUST NOT be0x0
.
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 | |||||
---|---|---|---|---|---|
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 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:
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
Copyright and related rights waived via CC0.