ERC1363 Payable Token

I would like to discuss the ERC1363 EIP.

The ERC1363 is an ERC20 compatible token that can make a callback on the receiver contract to notify token transfers or token approvals. It can be used to create a token payable crowdsale, selling services for tokens, paying invoices, making subscriptions, voting systems and many other purposes. Basically, it wants to define a way to make an action after a transfer or an approval action in a single transaction avoiding to introduce waiting time or to pay GAS twice.

It defines transferAndCall and transferFromAndCall functions that will call an onTransferReceived on a ERC1363Receiver contract.
It, also, defines approveAndCall functions that will call an onApprovalReceived on a ERC1363Spender contract.

This proposal is inspired by the ERC721 onERC721Received and ERC721TokenReceiver behaviors and doesn’t need for an external registry like the recently introduced ERC777.

Example ERC1363 Implementation:

pragma solidity ^0.5.10;

import "openzeppelin-solidity/contracts/utils/Address.sol";
import "openzeppelin-solidity/contracts/introspection/ERC165Checker.sol";
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";

import "./IERC1363.sol";
import "./IERC1363Receiver.sol";
import "./IERC1363Spender.sol";

contract ERC1363 is ERC20, IERC1363 {
    using Address for address;

    bytes4 internal constant _INTERFACE_ID_ERC1363_TRANSFER = 0x4bbee2df;
    bytes4 internal constant _INTERFACE_ID_ERC1363_APPROVE = 0xfb9ec8ce;
    bytes4 private constant _ERC1363_RECEIVED = 0x88a7ca5c;
    bytes4 private constant _ERC1363_APPROVED = 0x7b04a2d0;

    constructor() public {
        _registerInterface(_INTERFACE_ID_ERC1363_TRANSFER);
        _registerInterface(_INTERFACE_ID_ERC1363_APPROVE);
    }

    function transferAndCall(address to, uint256 value) public returns (bool) {
        return transferAndCall(to, value, "");
    }

    function transferAndCall(address to, uint256 value, bytes memory data) public returns (bool) {
        require(transfer(to, value));
        require(_checkAndCallTransfer(msg.sender, to, value, data));
        return true;
    }

    function transferFromAndCall(address from, address to, uint256 value) public returns (bool) {
        return transferFromAndCall(from, to, value, "");
    }

    function transferFromAndCall(address from, address to, uint256 value, bytes memory data) public returns (bool) {
        require(transferFrom(from, to, value));
        require(_checkAndCallTransfer(from, to, value, data));
        return true;
    }

    function approveAndCall(address spender, uint256 value) public returns (bool) {
        return approveAndCall(spender, value, "");
    }

    function approveAndCall(address spender, uint256 value, bytes memory data) public returns (bool) {
        approve(spender, value);
        require(_checkAndCallApprove(spender, value, data));
        return true;
    }

    function _checkAndCallTransfer(address from, address to, uint256 value, bytes memory data) internal returns (bool) {
        if (!to.isContract()) {
            return false;
        }
        bytes4 retval = IERC1363Receiver(to).onTransferReceived(
            msg.sender, from, value, data
        );
        return (retval == _ERC1363_RECEIVED);
    }

    function _checkAndCallApprove(address spender, uint256 value, bytes memory data) internal returns (bool) {
        if (!spender.isContract()) {
            return false;
        }
        bytes4 retval = IERC1363Spender(spender).onApprovalReceived(
            msg.sender, value, data
        );
        return (retval == _ERC1363_APPROVED);
    }
}

Below a “payable” contract implementation:

pragma solidity ^0.5.10;

import "openzeppelin-solidity/contracts/introspection/ERC165Checker.sol";

import "../token/ERC1363/IERC1363.sol";
import "../token/ERC1363/IERC1363Receiver.sol";
import "../token/ERC1363/IERC1363Spender.sol";

contract ERC1363Payable is IERC1363Receiver, IERC1363Spender, ERC165 {
    using ERC165Checker for address;

    bytes4 internal constant _INTERFACE_ID_ERC1363_RECEIVER = 0x88a7ca5c;
    bytes4 internal constant _INTERFACE_ID_ERC1363_SPENDER = 0x7b04a2d0;
    bytes4 private constant _INTERFACE_ID_ERC1363_TRANSFER = 0x4bbee2df;
    bytes4 private constant _INTERFACE_ID_ERC1363_APPROVE = 0xfb9ec8ce;

    event TokensReceived(
        address indexed operator,
        address indexed from,
        uint256 value,
        bytes data
    );

    event TokensApproved(
        address indexed owner,
        uint256 value,
        bytes data
    );

    // The ERC1363 token accepted
    IERC1363 private _acceptedToken;

    constructor(IERC1363 acceptedToken) public {
        require(address(acceptedToken) != address(0));
        require(
            acceptedToken.supportsInterface(_INTERFACE_ID_ERC1363_TRANSFER) &&
            acceptedToken.supportsInterface(_INTERFACE_ID_ERC1363_APPROVE)
        );

        _acceptedToken = acceptedToken;

        // register the supported interface to conform to IERC1363Receiver and IERC1363Spender via ERC165
        _registerInterface(_INTERFACE_ID_ERC1363_RECEIVER);
        _registerInterface(_INTERFACE_ID_ERC1363_SPENDER);
    }

   function onTransferReceived(address operator, address from, uint256 value, bytes memory data) public returns (bytes4) { // solhint-disable-line  max-line-length
        require(msg.sender == address(_acceptedToken));
        emit TokensReceived(operator, from, value, data);
        _transferReceived(operator, from, value, data);
        return _INTERFACE_ID_ERC1363_RECEIVER;
    }

    function onApprovalReceived(address owner, uint256 value, bytes memory data) public returns (bytes4) {
        require(msg.sender == address(_acceptedToken));
        emit TokensApproved(owner, value, data);
        _approvalReceived(owner, value, data);
        return _INTERFACE_ID_ERC1363_SPENDER;
    }

    function acceptedToken() public view returns (IERC1363) {
        return _acceptedToken;
    }

   function _transferReceived(address operator, address from, uint256 value, bytes memory data) internal {
        // optional override
    }

   function _approvalReceived(address owner, uint256 value, bytes memory data) internal {
        // optional override
    }
}

Here it is a package installable via npm.

I worked on ERC1363 for about a year using it in many projects.
For instance it has been included in FriendsFingers DAO where people can stake tokens after transfer or approve.

2 Likes

Hi @vittominacori

I like the idea of a lightweight ERC20 extension which provides approve and call type functionality for sending tokens to contracts.

Some of the community have been working on ERC20 extensions:
@augustol is working on ERC827
@k06a is working on ERC1003

There has been some discussion on the developing standards in the forum:

It would be great to get you all involved in the discussion to help move the developing standards forward.

1 Like