ERC777 hooks with accounts

Hello !
@abcoathup thank you so much for the example (Simple ERC777 token example) it was really helpful !

In the documentation it is specified that :
Both contracts and regular addresses can control and reject which token they send/receive by registering a tokensToSend/ tokensReceived hook. (Rejection is done by revert ing in the hook function.)

In the example you specified above , there is the test of sending tokens from an externally owned account to a contract that implements the IERC777Recipient, i was wondering how a regular address can register for example to “tokensToSend” hook ?

I mean since regular addresses don’t have code, it means i should create a smart contract just like you did that implements IERC777Sender? and call the setInterfaceImplementer function of the ERC-1820 registry giving it (regularAddress, TOKENS_SENDER_INTERFACE_HASH, address(this)) ?

I’m sorry this is still a bit blurry for me cause when you send tokens to a smart contract (which could act as an exchange for example) , it can be programmed to do stuff once you send tokens to it so it makes sense, but when a regular address registers to one of these hooks, what can it benefit from it ?

I hope my question is clear , thank you !

2 Likes

Hi @Sarah,

You should be able to register a tokensReceived and/or a tokensToSend hook for an externally owned account (regular address).

  1. Create a contract that implements IERC777Recipient or IERC777Sender

  2. Contract needs to also implement canImplementInterfaceForAddress (e.g. inherit from ERC1820Implementer)

    Any contract being registered as the implementation of an interface for a given address MUST implement said interface. In addition if it implements an interface on behalf of a different address, the contract MUST implement the ERC1820ImplementerInterface shown above.
    https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1820.md

  3. Get the interface hash

  4. Set the interface implementer (setInterfaceImplementer) on ERC1820 Registry for the externally owned account with the interface hash and the contract address.

This enables scenarios such as:

  • Rejecting sending tokens from an account by an operator.
  • Rejecting sending any (or specific) tokens to an account.
  • Rejecting sending tokens from an account above a certain amount.
1 Like

Hello @abcoathup , thank you for the explanation , i’ve been trying to make it work but i wasn’t able to do so. here is my Simple777Sender.sol :

pragma solidity ^0.5.0;

import "./IERC777.sol";
import "./introspection/IERC1820Registry.sol";
import "./IERC777Sender.sol";

contract Simple777Sender is IERC777Sender {
    address public themanager ;

    IERC1820Registry private _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
    bytes32 constant private TOKENS_SENDER_INTERFACE_HASH = keccak256("ERC777TokensSender");

    IERC777 private _token;

    event DoneStuffTokensToSend(address operator, address from, address to, uint256 amount, bytes userData, bytes operatorData);

    constructor (address token, address investor) public {
        _token = IERC777(token);
        themanager = _erc1820.getManager(investor);
      //  _erc1820.setManager(investor, address(this));
       // _erc1820.setInterfaceImplementer(investor, TOKENS_SENDER_INTERFACE_HASH, address(this));

    }

    function tokensToSend(
        address operator,
        address from,
        address to,
        uint256 amount,
        bytes calldata userData,
        bytes calldata operatorData
    ) external {
        require(msg.sender == address(_token), "Simple777Recipient: Invalid token");

        // do stuff
        emit DoneStuffTokensToSend(operator, from, to, amount, userData, operatorData);
    }
}

When i tried to call setInterfaceImplementer i got the error “Not the manger” so i tried to set the manager for address investor to be the address of the contract Simple777Sender , however i still Got the error " Not the manager" and it makes sense because going into the function setManager , there is

require(getManager(_addr) == msg.sender, "Not the manager");

and by default each address is the manager for itself until a new manager is set , means getManager(adr_investor) = adr_investor which is different from the msg.sender= adr_Simple777Sender , So i guess i can’t setManager() for my externally owned account(investor_adr) , and if i dnt do that then i can’t call setInterfaceImplementer without getting the Error “Not the manager”

I guess i’m obviously doing soomething wrong somewhere ,

1 Like

Hi @Sarah,

Regards setInterfaceImplementer, this needs to be called by the externally owned account, otherwise the externally owned account needs to call setManager to set the contract as the manager.


Apologies, I was missing a step, that I have added above.

Contract needs to also implement canImplementInterfaceForAddress (e.g. inherit from ERC1820Implementer)

I have added a Simple777Sender to my example:

Simple777Sender.sol

pragma solidity ^0.5.0;

import "@openzeppelin/contracts/token/ERC777/IERC777.sol";
import "@openzeppelin/contracts/introspection/IERC1820Registry.sol";
import "@openzeppelin/contracts/introspection/ERC1820Implementer.sol";
import "@openzeppelin/contracts/token/ERC777/IERC777Sender.sol";

contract Simple777Sender is IERC777Sender, ERC1820Implementer {

    bytes32 constant public TOKENS_SENDER_INTERFACE_HASH = keccak256("ERC777TokensSender");

    event DoneStuff(address operator, address from, address to, uint256 amount, bytes userData, bytes operatorData);

    function senderFor(address account) public {
        _registerInterfaceForAddress(TOKENS_SENDER_INTERFACE_HASH, account);
    }

    function tokensToSend(
        address operator,
        address from,
        address to,
        uint256 amount,
        bytes calldata userData,
        bytes calldata operatorData
    ) external {

        // do stuff
        emit DoneStuff(operator, from, to, amount, userData, operatorData);
    }
}

Simple777Sender.test.js

const { singletons, BN, expectEvent } = require('openzeppelin-test-helpers');

const Simple777Token = artifacts.require('Simple777Token');
const Simple777Sender = artifacts.require('Simple777Sender');

contract('Simple777Sender', function ([_, registryFunder, creator, holder, recipient]) {
  const data = web3.utils.sha3('777TestData');

  beforeEach(async function () {
    this.erc1820 = await singletons.ERC1820Registry(registryFunder);
    this.token = await Simple777Token.new({ from: creator });
    const amount = new BN(10000);
    await this.token.send(holder, amount, data, { from: creator });
    this.sender = await Simple777Sender.new({ from: creator });
  });

  it('sends from an externally-owned account', async function () {
    const amount = new BN(1000);
    const tokensSenderInterfaceHash = await this.sender.TOKENS_SENDER_INTERFACE_HASH();

    await this.sender.senderFor(holder);
    await this.erc1820.setInterfaceImplementer(holder, tokensSenderInterfaceHash, this.sender.address, { from: holder });

    const receipt = await this.token.send(recipient, amount, data, { from: holder });
    await expectEvent.inTransaction(receipt.tx, Simple777Sender, 'DoneStuff', { from: holder, to: recipient, amount: amount, userData: data, operatorData: null });

    const recipientBalance = await this.token.balanceOf(recipient);
    recipientBalance.should.be.bignumber.equal(amount);
  });
});
1 Like

@abcoathup Thanks !! :smiley:

1 Like