ERC-777 Advanced token standard (hooks and operators) (Token Standard)
Final · Tokens · created 2017-11-20
A token standard that is compatible with ERC-20, notifies both the sender and the recipient (hooks) when tokens are sent, and lets other addresses (operators) send or burn tokens on a holder's behalf.
Why it is needed
Safely sending ERC-20 tokens to a contract requires two calls, approve and transferFrom, and the standard does not define any additional way for the two parties to communicate. Holders often confuse transfer with approve/transferFrom, and tokens sent to a contract with transfer are easily locked. The recipient also receives no notification when tokens arrive, so it cannot react immediately, and it has no way to reject unwanted tokens. ERC-20 does not clearly define how tokens are minted and burned, so totalSupply can sometimes diverge from the actual circulating amount. To address these limitations, ERC-777 lets tokens be sent with a single send call, like sending ether, enables notification and rejection through hooks, and defines the whole token lifecycle from minting to burning.
What to implement
function name() external view returns (string memory);: Returns the name of the token.function symbol() external view returns (string memory);: Returns the symbol of the token.function totalSupply() external view returns (uint256);: Returns the total amount of tokens minted. It must equal the sum of the balances of all addresses.function balanceOf(address holder) external view returns (uint256);: Returns the token balance held by the holder address. It must be zero or greater.function granularity() external view returns (uint256);: Returns the smallest indivisible unit of the token. All balances and movement amounts must be multiples of this value.function defaultOperators() external view returns (address[] memory);: Returns the list of default operators defined by the token contract. If there are none, it returns an empty list.function authorizeOperator(address operator) external;: Authorizes the operator address as an operator of msg.sender, allowing it to send and burn tokens on their behalf.function revokeOperator(address operator) external;: Revokes the operator rights of the operator address. An address cannot revoke its own rights over itself.function send(address to, uint256 amount, bytes calldata data) external;: Sends amount of msg.sender's tokens to the to address. The data field can carry information from the holder.function burn(uint256 amount, bytes calldata data) external;: Burns amount of msg.sender's tokens.event AuthorizedOperator(address indexed operator, address indexed holder);: Signals that the holder has authorized the operator as an operator.event RevokedOperator(address indexed operator, address indexed holder);: Signals that the holder has revoked the operator rights of the operator.
In depth
Register in the ERC-1820 registry
The token contract must register the ERC777Token interface (hash 0xac7fbab5f54a3ca8194167523c6753bfeb96a445279294b6125b68cce2177054) for its own address by calling setInterfaceImplementer on the ERC-1820 registry. If there is a switch that turns ERC-777 functionality on and off, registration and deregistration must accompany each change of the switch, and deregistration is done by setting the implementer to 0x0. The hooks of holders and recipients are also registered in the same registry, using the interface hashes for ERC777TokensSender and ERC777TokensRecipient. Because the registry is used, regular addresses that are not contracts can also benefit from hooks through a proxy contract.
Units and decimals: decimals is always 18
All quantities and balances are unsigned integers and are stored internally in units of 1E-18 of the token. The display unit shown to users is 10^18 times the internal unit, the same relationship as wei to ether. For example, a balance of 500,000,000,000,000,000 should be shown on screen as 0.5 tokens, and to send 0.3 tokens you must pass 300,000,000,000,000,000. An ERC-20 compatible token must implement the decimals function and must always return 18. In a pure ERC-777 token, decimals is optional and the value 18 applies implicitly.
granularity: the smallest indivisible unit
granularity is the smallest bundle of tokens, in internal units, that can be minted, sent or burned. It must be set at creation, can never be changed afterward, and must be at least 1. Every balance must be a multiple of granularity, and every amount that is minted, sent or burned must also be a multiple. An operation that would create a balance that is not a multiple is invalid and the transaction must revert. Unless there is a specific reason, the original text recommends setting this value to 1 so that the token is fully divisible.
Operators and default operators
An operator is an address that can send and burn tokens on behalf of a holder. Every address is always its own operator, and this authority cannot be revoked. A holder approves and revokes operators with authorizeOperator and revokeOperator, and the AuthorizedOperator and RevokedOperator events must be emitted each time. A token may have default operators that are implicitly authorized for all holders. The list of default operators is fixed at contract creation and cannot be added to or removed from afterward. However, a holder can revoke a default operator individually and authorize it again. Default operators are intended for uses such as letting a holder move tokens without needing ether.
Transfer rules and hook call order
On a send, the holder's balance decreases by amount, the recipient's balance increases by the same amount, and a Sent event must be emitted. A send by an unauthorized operator, a resulting balance that is not a multiple of granularity, a holder or recipient of 0x0, an insufficient balance, and a hook revert all cause the send to revert. If the recipient is a contract that has not registered ERC777TokensRecipient, the send must revert. The call order matters. tokensToSend must be called before the state update and tokensReceived after it, so balanceOf inside tokensToSend returns the balance before the send and balanceOf inside tokensReceived returns the balance after the send. data and operatorData must not change during the send, and a send of 0 tokens must be treated as a valid ordinary send.
Minting and burning
The minting function differs from token to token, so the standard deliberately does not define it, but there are rules. On minting, the total supply and the recipient's balance increase and a Minted event must be emitted, and if the recipient has registered a hook, tokensReceived must be called. The initial supply is also treated as minting, so it requires the Minted event and the hook call. For burning, the two functions burn and operatorBurn must be implemented, the total supply and the holder's balance decrease, and a Burned event must be emitted. If the holder has registered one, the tokensToSend hook is called. Neither minting nor burning can be done against the 0x0 address, and for an ERC-20 compatible token it is recommended to also emit a Transfer event with from or to set to 0x0.
Compatibility with ERC-20 and duplicate events
ERC-777 is backward compatible with ERC-20, and one contract can implement both standards. To avoid confusion, it uses the names send/operatorSend instead of transfer/transferFrom. If ERC-20 is also implemented, the ERC20Token interface must also be registered in ERC-1820, and the hooks must be called, if registered, when ERC-20 transfer and transferFrom are called. However, sending to a contract without a hook through ERC-20 transfer should continue to proceed (SHOULD), so tokens can become locked. A single token movement can emit both an ERC-20 Transfer event and an ERC-777 Sent/Minted/Burned event, so an application should count only Transfer if it treats the token as ERC-20, and only Sent, Minted and Burned if it treats the token as ERC-777, to avoid double counting.
Note on gas consumption
Because of hook calls, the gas consumed by sends, mints and burns is hard to know in advance. The original text recommends that dapps and wallets first estimate the required gas with eth_estimateGas so that they avoid running out of gas mid-transaction.