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

In case anyone from future is reading this thread. the ERC-1363 was added to OZ in 2021

However it seems the ERC1363 Token implementation was not added to OZ, only the interface was.

Would it be something OZ is interested in adding?

Here it is an official open PR to add ERC1363. It was blocked by the discussion and it is now pending until v5 will be released, but maybe it is now different from the new codebase.

I've also proposed a complete PR to introduce but is was closed in favor of the previous one.

Anyway if you are looking for an updated and tested ERC1363 implementation you can check this repo https://github.com/vittominacori/erc1363-payable-token

I will be happy to PR it again into OZ once the new v5 will be released.

This is something we have on our 5.0 roadmap. Perhaps it won't make it until 5.1, but just wanted to highlight we're still interested in including this EIP. We'll continue this discussion (that's already lonng) after 5.0.

These are the current relevant URLs in the repository: