ERC777

As of v4.9, OpenZeppelin’s implementation of ERC-777 is deprecated and will be removed in the next major release.

Like ERC20, ERC777 is a standard for fungible tokens, and is focused around allowing more complex interactions when trading tokens. More generally, it brings tokens and Ether closer together by providing the equivalent of a msg.value field, but for tokens.

The standard also brings multiple quality-of-life improvements, such as getting rid of the confusion around decimals, minting and burning with proper events, among others, but its killer feature is receive hooks. A hook is simply a function in a contract that is called when tokens are sent to it, meaning accounts and contracts can react to receiving tokens.

This enables a lot of interesting use cases, including atomic purchases using tokens (no need to do approve and transferFrom in two separate transactions), rejecting reception of tokens (by reverting on the hook call), redirecting the received tokens to other addresses (similarly to how PaymentSplitter does it), among many others.

Furthermore, since contracts are required to implement these hooks in order to receive tokens, no tokens can get stuck in a contract that is unaware of the ERC777 protocol, as has happened countless times when using ERC20s.

What If I Already Use ERC20?

The standard has you covered! The ERC777 standard is backwards compatible with ERC20, meaning you can interact with these tokens as if they were ERC20, using the standard functions, while still getting all of the niceties, including send hooks. See the EIP’s Backwards Compatibility section to learn more.

Constructing an ERC777 Token Contract

We will replicate the GLD example of the ERC20 guide, this time using ERC777. As always, check out the API reference to learn more about the details of each function.

// contracts/GLDToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC777/ERC777.sol";

contract GLDToken is ERC777 {
    constructor(uint256 initialSupply, address[] memory defaultOperators)
        ERC777("Gold", "GLD", defaultOperators)
    {
        _mint(msg.sender, initialSupply, "", "");
    }
}

In this case, we’ll be extending from the ERC777 contract, which provides an implementation with compatibility support for ERC20. The API is quite similar to that of ERC777, and we’ll once again make use of _mint to assign the initialSupply to the deployer account. Unlike ERC20’s _mint, this one includes some extra parameters, but you can safely ignore those for now.

You’ll notice both name and symbol are assigned, but not decimals. The ERC777 specification makes it mandatory to include support for these functions (unlike ERC20, where it is optional and we had to include ERC20Detailed), but also mandates that decimals always returns a fixed value of 18, so there’s no need to set it ourselves. For a review of decimals's role and importance, refer back to our ERC20 guide.

Finally, we’ll need to set the defaultOperators: special accounts (usually other smart contracts) that will be able to transfer tokens on behalf of their holders. If you’re not planning on using operators in your token, you can simply pass an empty array. Stay tuned for an upcoming in-depth guide on ERC777 operators!

That’s it for a basic token contract! We can now deploy it, and use the same balanceOf method to query the deployer’s balance:

> GLDToken.balanceOf(deployerAddress)
1000

To move tokens from one account to another, we can use both ERC20's transfer method, or the new ERC777's send, which fulfills a very similar role, but adds an optional data field:

> GLDToken.transfer(otherAddress, 300)
> GLDToken.send(otherAddress, 300, "")
> GLDToken.balanceOf(otherAddress)
600
> GLDToken.balanceOf(deployerAddress)
400

Sending Tokens to Contracts

A key difference when using send is that token transfers to other contracts may revert with the following message:

ERC777: token recipient contract has no implementer for ERC777TokensRecipient

This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC777 protocol, so transfers to it are disabled to prevent tokens from being locked forever. As an example, the Golem contract currently holds over 350k GNT tokens, worth multiple tens of thousands of dollars, and lacks methods to get them out of there. This has happened to virtually every ERC20-backed project, usually due to user error.

An upcoming guide will cover how a contract can register itself as a recipient, send and receive hooks, and other advanced features of ERC777!