Implementing ERC777 Receive Hooks / Missing ERC777 Guides

I’m trying to design a type of fundraising tool to be used by musicians. I’d like to build a contract that can receive DAI and return a redeemable token to the sender. The artist then withdraws the DAI and later deposits royalties back to the contract, which are redeemable by the original backers with their tokens.

It seems like using an ERC777 token is a good way to approach this, and trying to use the Receive Hook that mints and returns a token upon receipt of DAI would be a good way to do it.

I’m having trouble finding enough documentation on ERC777 hooks, in particular the receive hooks. Is it just me or is the standard not very well documented yet? There is a tutorial for making an ERC777 GLD token but i’ve only found one example of a beforetransfer hook. Nothing about receive hooks despite their apparent usefulness.

In the OZ docs i can only find:

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

Am I missing something? This standard has been around for a while but I can find almost no useful guides on it, Is the ERC777 dead somehow?

1 Like

The EIP777 can be a bit confusing, specially because it’s a long read and introduces a lot of things.

My understanding of the EIP is that it basically tries to fix problems that EIP20 has while remaining backwards compatible with it - to name some of these problems: the approve-transfer mechanism , that essentially requires two transactions to send tokens on behalf of another address, and sending tokens to contracts that cannot receive them thus locking them forever.

To solve the first problem (approve-transfer) it introduces a new concept that they call operators.
An operator is, in essence, an address that has the right to move tokens on behalf of another address. As such every address is, by default, an operator for itself (i.e I can move my own tokens by default) and this right cannot be revoked.

To allow this, it introduces the functionality to give, authorizeOperator(), and revoke, revokeOperator(), operator rights and also introduces the operatorSend() function that allows an operator to send tokens on behalf of another address.

To solve the second problem (sending tokens to contracts that cannot receive them) it introduces the concept of hooks. These hooks are basically functions that get called when tokens are sent, burned or minted and that are used to notify sender and receiver about the operation that is going to take place - this not only solves this problem but it also allows far more complex operations with the tokens, because contracts can get notified about these token movements and react accordingly.

The hook that notifies the sender is called tokensToSend() and the hook that notifies the receiver is tokensReceived()

Now, to your particular question.

The tokensReceived() hook allows contracts to receive ERC777-compliant tokens and react accordingly, to do so they MUST register themselves as ERC777Recipient implementers by calling the setInterfaceImplementer() function on a ERC1820 Registry (notice that I said a but there is actually only one registry per network according to the EIP). Notice, that if you try to send tokens to a contract that does not implement the interface, the transaction will fail.

You can read more about the EIP rationale.

To clarify:

EIP1820 introduces the concept of a registry contract which allows contracts to register themselves as interface implementers, for example - I can have my contract register itself as an implementer of the ERC777Recipient interface allowing other contracts to query the registry before trying to interact with me - this allows for more complex operations and can also, potentially, prevent errors.
The EIP also allows addresses to register other addresses as interface implementers on their behalf which allows externally owned accounts to perform more complex operations by having a contract implement - for example - the hooks on their behalf, and not only that, but you can make your old and non-upgradable contract be compatible with another interface by just registering another contract that implements that interface on your behalf.

Notice that EIP777 depends on EIP1820, but the registry contract is completely generic and it would allow you to register other interfaces too.

See EIP1820 ratione.

Hope that clarifies some of your concerns - it’s quite a long EIP and kinda tricky because it has a lot of moving pieces.

Out of curiosity:

Why do you think this particular standard suits your particular use-case best ? I’m not saying it doesn’t, I’m just curious about what you are looking for in terms of functionality.

Take care!

2 Likes

@Madness Thank you for such a thoughtful reply.

I’ve been considering the ERC777 standard because the narrative around it seems to be something like an “updated” ERC20. But the more I look into it, the more it seems the specific updates do not really help my use case that much. Seems making an 1.0 version in ERC20 may be a better bet, or at least a clearer path.

How to approach the goal has got me kind of stumped actually. It seems everything I consider has clear compromises or unanswered questions, I suppose to only way is to start building while acknowledging that much, or all, of it will change if It is to manifest in a satisfying way.

As far as functionality goes, I want to have a secure way for many users to deposit funds and withdraw a commensurate amount at a later date. It seems like a token swap would be the right goal for this, where at the time of deposit a redeemable token is minted, then that redeemer token is burned upon redemption of the initial funding amount.

I was conceiving the tokensReceived() hook as an efficient path to this but maybe I was misunderstanding its purpose.

1 Like

I’ve been considering the ERC777 standard because the narrative around it seems to be something like an “updated” ERC20. But the more I look into it, the more it seems the specific updates do not really help my use case that much. Seems making an 1.0 version in ERC20 may be a better bet, or at least a clearer path.

I think that keeping things simple is a good starting point, specially when there are so many security considerations that have to be made when storing money on-chain.

How to approach the goal has got me kind of stumped actually. It seems everything I consider has clear compromises or unanswered questions, I suppose to only way is to start building while acknowledging that much, or all, of it will change if It is to manifest in a satisfying way.

Starting a first version with the ERC20, I think, is a very good idea, once you have everything in place you will get a better view and understanding of things and it will allow you to notice those things that are, perhaps, missing or that were not as good as you thought they would.

As far as functionality goes, I want to have a secure way for many users to deposit funds and withdraw a commensurate amount at a later date. It seems like a token swap would be the right goal for this, where at the time of deposit a redeemable token is minted, then that redeemer token is burned upon redemption of the initial funding amount.

Yeah, a mint/redeem mechanism can work, which would also allow backers to trade the right to redeem royalties between them (if that's something that you might look for) another alternative would be a deposit/withdraw mechanism were you would deposit tokens into the contract and then whenever you want to back an artist you simply call a function and those funds get transferred then whenever you want to redeem some royalties the contract would allocate some funds for you.

Whatever mechanism you decide to implement - I think that the key resides in a good articulation of how the backing/funding of an artist works, how the claim to those off-chain royalties is defined and more importantly how the royalties get distributed.

I was conceiving the tokensReceived() hook as an efficient path to this but maybe I was misunderstanding its purpose.

I think that, if you don't want to use EIP777, a function that distributes the royalties between funders when they are generated can do the same, then funders can just withdraw those royalties (this way the distribution can happen in the contract) if they want to or keep on using them for backing or whatever other functionality is available to them.

2 Likes

Hi @Yorkemartin,

One of the biggest issues with ERC20 is the approve/transferFrom two transaction workflow. There has been several attempts at a lightweight standard to implement an approve and call mechanism. ERC777 solves this with hooks. There is potential for a standard based on DAI’s permit (Add ERC20 permit() function)

I see ERC777 as a powerful upgrade to ERC20. Though reentrancy is something to be very aware of (e.g. Uniswap V1: https://blog.openzeppelin.com/exploiting-uniswap-from-reentrancy-to-actual-profit/)

PoolTogether uses ERC777 and you could look at the audit report and PoolTogether’s audit disclosures.

@miohtama decided to go with ERC777. (see their thoughts: Emerging standards and recommendations for approveAndCall, transferAndCall EIP-777 and so on)

I put together a Simple ERC777 token example and also an OpenZeppelin Contracts 3 example: Simple ERC777 token example using OpenZeppelin Contracts 3.x

@madness thanks again for the detailed input to the discussion :pray:.

1 Like

@abcoathup Always happy to help :smiley:

1 Like

Hi,

FirstBlood ERC-777 contracts are here - for the reference

The another added benefit, besides having a single TX interaction with smart contracts, is that ERC-777 recipients can reject token transfers on smart contracts. There are countless of ERC-20 tokens lost when send on a smart contract that does not support them.

1 Like

for anyone else finding this, I decided to just continue with erc20 because of the lack of adoption / guides around erc777. Maybe if I get more expertise later I’ll revisit.

2 Likes