In this post, we will look at how ERC777 improves upon ERC20, and how it does that with the help of ERC1820. In doing so, we will notice that ERC1820 has a really cool way of being deployed on the blockchain, which we will cover here too!
ERC777: Token Standard
We may start by quoting the abstract as seen on the original proposal:
This standard defines a new way to interact with a token contract while remaining backward compatible with ERC-20. 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 ERC-1820 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.
If you are reading this, chances are you already know about ERC-20 to some extent. You may also know that ERC-20 has some shortcomings with it:
- The allowance mechanism causes redundant transactions to be made (i.e.
approve
andtransferFrom
). - No operators to transfer tokens on behalf of you, which would be most useful in a DEX or marketplace.
- Contracts do not need be aware of ERC-20 to receive tokens. This may cause tokens to be locked in a contract, if there is no way to withdraw them.
ERC-777 addresses all these problems, while remaining backwards compatible with existing ERC-20 functionality. The operator logic can be It makes use of ERC-1820 to achieve these, so let's take a look at that now.
ERC1820: Pseudo-Introspection Registry Contract
Let us again quote the original proposal summary:
This standard defines a universal registry smart contract where any address (contract or regular account) can register which interface it supports and which smart contract is responsible for its implementation. This standard keeps backward compatibility with ERC-165.
Think of ERC1820 as a record book that lives per blockchain, and everyone knows where it is located. Contract in that chain can add entries to this mapping, saying that "hey, I am 0x123..ABC
and I support this and that interface, if anyone asks". This way, if you were to send a token to some contract, you can query that contract in ERC-1820 to see whether they actually declare support for that token; in which case you can safely send the tokens.
Note that they could be lying, they could say "hey I support ERC-777" but do not actually implement the code itself. That is an acceptable risk at this point.
ERC-1820 is deployed once on each chain, at a pre-defined address. The deployment of ERC-1820 happens in a very cool way, called a "keyless deployment" (also known as Nick's Method. With this method, a contract can be deployed where everyone can agree that the owner of this contract is not accessible; i.e. the private key of original deployer is not known.
The trick is to generate a custom transaction with a pre-defined deterministic signature. Indeed, signatures are generated from a private key; but you can also write anything (like your name and favorite food) to be the signature and theoretically it should be mapped from some private key out there, that which no ones knows. Please see the relevant section of the proposal to learn about the deployment itself.
ERC777 Hooks
Another benefit of this standard is the usage of "hooks". You can write custom code that runs whenever you send and/or receive ERC-720 tokens from a contract. This is possible thanks to ERC-1820, where contracts can declare that they support either of the following interfaces:
-
ERC777TokensSender
for hooks that execute when a token is sent. -
ERC777TokensRecipient
for hooks that execute when a token is received.
Hooks have many great use-cases, for example you could have a staking logic execute whenever a token is sent to the contract via this hook.
Show me the code: ERC-777
OpenZeppelin already has an ERC-777 implementation. We can use theirs to quickly write one:
import "@openzeppelin/contracts/token/ERC777/ERC777.sol";
contract MyERC777 is ERC777 {
constructor(
string memory name_,
string memory symbol_,
uint256 supply_,
address[] memory ops_ // default operators
) ERC777(name_, symbol_, ops_) {
_mint(msg.sender, supply_, "", "");
}
}
This contract mints all the tokens to the contract deployer. Token name and symbol is given in the constructor. All the ERC-777 logic is handled within the ERC777 contract of OpenZeppelin.
Now, let us implement a recipient contract that keeps track of the last address that has sent a token to it, along with total number of instances that it has received a token.
contract MyERC777Recipient is IERC777Recipient, ERC1820Implementer {
IERC1820Registry internal constant _ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
uint256 public numTimesReceived = 0;
address public lastReceivedFrom;
constructor() {
// register to ERC1820 registry
_ERC1820_REGISTRY.setInterfaceImplementer(
address(this),
_ERC1820_REGISTRY.interfaceHash("ERC777TokensRecipient"),
address(this)
);
}
function tokensReceived(
address, /* operator */
address from,
address, /* to */
uint256, /* amount */
bytes calldata, /* userData */
bytes calldata /* operatorData */
) external override {
lastReceivedFrom = from;
numTimesReceived++;
}
}
Within the constructor, we register the contract within the ERC-1820 to declare that it is supporting the ERC777TokensRecipient
interface. In other words, it is saying "you can freely send me ERC-777 tokens, I am aware of that contract standard".
The tokensReceived
function is the function that will be called when a token is received. Some arguments are /* commented out */
like this to silence the compiler error for them not being used.
The body of this function is dead simple, just keep record of last received from address, and increment the number of times a token has been received.
Show me the code: Deploying ERC-1820
Deploying the ERC-1820 is described in the proposal quite well, and here is the TypeScript code using ethers.js
to deploy it:
export default async function deployERC1820(issuer: SignerWithAddress) {
const code = await ethers.provider.send('eth_getCode', [constants.ERC1820.address, 'latest']);
if (code === '0x') {
await issuer.sendTransaction({
to: '0xa990077c3205cbDf861e17Fa532eeB069cE9fF96',
value: parseEther('0.08'),
});
// payload is quite long, do not bother to scroll :)
await ethers.provider.send('eth_sendRawTransaction', '0xf90a388085174876e800830c35008080b909e5608060405234801561001057600080fd5b506109c5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a5576000357c010000000000000000000000000000000000000000000000000000000090048063a41e7d5111610078578063a41e7d51146101d4578063aabbb8ca1461020a578063b705676514610236578063f712f3e814610280576100a5565b806329965a1d146100aa5780633d584063146100e25780635df8122f1461012457806365ba36c114610152575b600080fd5b6100e0600480360360608110156100c057600080fd5b50600160a060020a038135811691602081013591604090910135166102b6565b005b610108600480360360208110156100f857600080fd5b5035600160a060020a0316610570565b60408051600160a060020a039092168252519081900360200190f35b6100e06004803603604081101561013a57600080fd5b50600160a060020a03813581169160200135166105bc565b6101c26004803603602081101561016857600080fd5b81019060208101813564010000000081111561018357600080fd5b82018360208201111561019557600080fd5b803590602001918460018302840111640100000000831117156101b757600080fd5b5090925090506106b3565b60408051918252519081900360200190f35b6100e0600480360360408110156101ea57600080fd5b508035600160a060020a03169060200135600160e060020a0319166106ee565b6101086004803603604081101561022057600080fd5b50600160a060020a038135169060200135610778565b61026c6004803603604081101561024c57600080fd5b508035600160a060020a03169060200135600160e060020a0319166107ef565b604080519115158252519081900360200190f35b61026c6004803603604081101561029657600080fd5b508035600160a060020a03169060200135600160e060020a0319166108aa565b6000600160a060020a038416156102cd57836102cf565b335b9050336102db82610570565b600160a060020a031614610339576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b6103428361092a565b15610397576040805160e560020a62461bcd02815260206004820152601a60248201527f4d757374206e6f7420626520616e204552433136352068617368000000000000604482015290519081900360640190fd5b600160a060020a038216158015906103b85750600160a060020a0382163314155b156104ff5760405160200180807f455243313832305f4143434550545f4d4147494300000000000000000000000081525060140190506040516020818303038152906040528051906020012082600160a060020a031663249cb3fa85846040518363ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018083815260200182600160a060020a0316600160a060020a031681526020019250505060206040518083038186803b15801561047e57600080fd5b505afa158015610492573d6000803e3d6000fd5b505050506040513d60208110156104a857600080fd5b5051146104ff576040805160e560020a62461bcd02815260206004820181905260248201527f446f6573206e6f7420696d706c656d656e742074686520696e74657266616365604482015290519081900360640190fd5b600160a060020a03818116600081815260208181526040808320888452909152808220805473ffffffffffffffffffffffffffffffffffffffff19169487169485179055518692917f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db15391a450505050565b600160a060020a03818116600090815260016020526040812054909116151561059a5750806105b7565b50600160a060020a03808216600090815260016020526040902054165b919050565b336105c683610570565b600160a060020a031614610624576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b81600160a060020a031681600160a060020a0316146106435780610646565b60005b600160a060020a03838116600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff19169585169590951790945592519184169290917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a43509190a35050565b600082826040516020018083838082843780830192505050925050506040516020818303038152906040528051906020012090505b92915050565b6106f882826107ef565b610703576000610705565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b600080600160a060020a038416156107905783610792565b335b905061079d8361092a565b156107c357826107ad82826108aa565b6107b85760006107ba565b815b925050506106e8565b600160a060020a0390811660009081526020818152604080832086845290915290205416905092915050565b6000808061081d857f01ffc9a70000000000000000000000000000000000000000000000000000000061094c565b909250905081158061082d575080155b1561083d576000925050506106e8565b61084f85600160e060020a031961094c565b909250905081158061086057508015155b15610870576000925050506106e8565b61087a858561094c565b909250905060018214801561088f5750806001145b1561089f576001925050506106e8565b506000949350505050565b600160a060020a0382166000908152600260209081526040808320600160e060020a03198516845290915281205460ff1615156108f2576108eb83836107ef565b90506106e8565b50600160a060020a03808316600081815260208181526040808320600160e060020a0319871684529091529020549091161492915050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff161590565b6040517f01ffc9a7000000000000000000000000000000000000000000000000000000008082526004820183905260009182919060208160248189617530fa90519096909550935050505056fea165627a7a72305820377f4a2d4301ede9949f163f319021a6e9c687c292a5e2b2c4734c126b524e6c00291ba01820182018201820182018201820182018201820182018201820182018201820a01820182018201820182018201820182018201820182018201820182018201820');
}
}
This code is particularly useful if you are working on your local blockchain. ERC-777 will ALWAYS look at the address 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
for ERC-1820, so you should make the deployment as described.
I have implemented these using Hardhat + TypeScript in my repository: https://github.com/erhant/contract-playground, please do check it out! You can also find the tests for ERC-777, which is a great source if you would like to understand the code to interact with an ERC-777 contract.
Happy coding :)
Top comments (1)
Guess it would be ERC777 :)