akash11

ERC-777: The Token Standard That Tried to Fix ERC-20

A token standard that extends ERC-20 with hooks, operators, and better control mechanisms, designed to eliminate common pitfalls while maintaining backward compatibility.

Published on 22 November, 2025

Categories:

SolidityToken

What ERC-777 Actually Is

ERC-777 appeared in 2017 as an attempt to solve real problems developers faced with ERC-20 tokens. The main issue? ERC-20 tokens had no way to notify a contract when tokens arrived. This led to tokens getting stuck in contracts or requiring that clunky two-step approve/transferFrom dance.
Think of ERC-20 like sending a package without tracking. You ship it, but the recipient has no idea it's coming. ERC-777 adds the notification system.

Core Components

Hooks

The fundamental innovation in ERC-777 is hooks. When you send tokens, the standard checks if the recipient is a contract. If it is, ERC-777 calls a specific function on that contract to say "hey, tokens are coming your way."
function tokensReceived( address operator, address from, address to, uint256 amount, bytes calldata userData, bytes calldata operatorData ) external;
This hook runs automatically. The receiving contract must implement tokensReceived or the transfer fails. No more tokens disappearing into contracts that can't handle them.

Operators

ERC-777 introduces operators, which are addresses you authorize to send tokens on your behalf. Different from ERC-20's approve mechanism because operators have full control until you revoke them.
// Authorize an operator function authorizeOperator(address operator) external; // Revoke an operator function revokeOperator(address operator) external; // Check if someone is your operator function isOperatorFor(address operator, address tokenHolder) external view returns (bool);
Default operators can exist too. These are addresses the token creator designates as operators for all token holders. Useful for things like automatic fee collection or protocol-level features.

Send vs Transfer

ERC-777 uses send instead of transfer. Same basic job, different execution.
function send(address recipient, uint256 amount, bytes calldata data) external;
The data parameter lets you attach information to the transfer. Like adding a memo to a bank transfer.

How Token Movement Works

Here's the flow when you send ERC-777 tokens:
User calls send()
    |
    v
Check sender has balance
    |
    v
Is sender a contract?
   / \
  yes no
  |    |
  v    v
Call tokensToSend  Skip
hook if implemented
    |
    v
Deduct from sender
Add to recipient
    |
    v
Is recipient a contract?
   / \
  yes no
  |    |
  v    v
Call tokensReceived  Done
hook (required)
    |
    v
Done
The tokensToSend hook is optional for senders. The tokensReceived hook is mandatory for recipients.

ERC-1820 Registry

ERC-777 depends on ERC-1820, a global registry contract deployed at the same address on every EVM chain. This registry tells you which interfaces an address implements.
// Check if address implements ERC777TokensRecipient bytes32 constant TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); address implementer = erc1820Registry.getInterfaceImplementer( account, TOKENS_RECIPIENT_INTERFACE_HASH );
Every ERC-777 contract must register itself with ERC-1820. Every contract that wants to receive ERC-777 tokens must register its tokensReceived implementation.

Backward Compatibility

ERC-777 maintains ERC-20 compatibility. An ERC-777 token implements all ERC-20 functions, so old contracts and interfaces work without changes.
// ERC-20 functions still work function transfer(address recipient, uint256 amount) external returns (bool); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
However, when you use ERC-20 functions on an ERC-777 token, you lose the hook functionality. The transfer happens but no tokensReceived call occurs.

Real Implementation Example

// Simple receiver contract contract TokenVault { IERC777 public token; IERC1820Registry private erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); constructor(address tokenAddress) { token = IERC777(tokenAddress); // Register that we implement tokensReceived bytes32 TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); erc1820.setInterfaceImplementer( address(this), TOKENS_RECIPIENT_INTERFACE_HASH, address(this) ); } function tokensReceived( address operator, address from, address to, uint256 amount, bytes calldata userData, bytes calldata operatorData ) external { require(msg.sender == address(token), "Invalid token"); // Do something with the tokens // Maybe parse userData to determine action // Tokens are already in this contract } }

Security Considerations

The reentrancy risk is significant. When you send tokens, the recipient's tokensReceived function executes before the transfer completes in some implementations. This can enable reentrancy attacks.
// Vulnerable pattern function withdraw(uint256 amount) external { token.send(msg.sender, amount, ""); // Calls tokensReceived userBalance[msg.sender] -= amount; // State change after external call }
The hook mechanism also means gas costs for transfers are higher and unpredictable. You're calling unknown code in the recipient contract.
Some major protocols explicitly don't support ERC-777 because of these concerns. Uniswap, for example, only works with ERC-20.

Why ERC-777 Didn't Dominate

Despite solving real problems, ERC-777 never achieved widespread adoption. The added complexity, higher gas costs, and security concerns made developers cautious. The infamous 2020 reentrancy attacks on several DeFi protocols using ERC-777 tokens damaged its reputation.
Most new projects stick with ERC-20 or jump straight to newer standards. ERC-777 occupies an awkward middle ground, too complex for simple use cases but not flexible enough for advanced ones.

Conclusion

ERC-777 introduced valuable concepts like hooks and operators to improve token interactions, but practical concerns around security and complexity limited its adoption. The standard remains available and functional, serving specific use cases where its features justify the additional overhead and risk.

Copyright © 2025

Akash Vaghela