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.