ERC-1363 enhances the ERC-20 standard by enabling smart contracts to detect and respond to token transfers. This capability allows for more seamless interactions with decentralized applications (dApps) without requiring multiple transactions or pre-approvals.
What Problem Does ERC-1363 Solve?
When a user transfers ERC-20 tokens to a contract, the contract cannot automatically recognize or account for the transfer because there is no built-in mechanism to identify the sender. While transfer events are emitted, they are only accessible off-chain. Smart contracts cannot read events without an oracle.
Traditional Solution: Using transferFrom for Self-Transfer
The conventional workaround requires the token sender to approve the receiving contract to spend their tokens. The contract then calls transferFrom to move tokens from the user’s account to itself.
contract ReceivingContract {
function deposit(uint256 amount) external {
ERC20(token).transferFrom(msg.sender, address(this), amount);
deposits[msg.sender] += amount;
}
}This approach introduces extra gas costs for the approval step and potential security risks if the approval is not revoked.
Transfer Hooks
Transfer hooks are predefined functions in a receiving contract that are automatically called when it receives tokens. The token contract invokes these functions after a transfer, ensuring the recipient is notified.
If the hook function doesn’t exist, reverts, or returns an unexpected value, the entire transfer transaction fails.
Users familiar with ERC-721’s onERC721Received will find this concept similar.
How ERC-1363 Extends ERC-20
ERC-1363 adds new functions to ERC-20 that trigger transfer hooks upon token movement. Receiving contracts must implement these hooks according to the standard.
IERC1363Receiver Interface
Contracts expecting to receive ERC-1363 tokens must implement the IERC1363Receiver interface, which includes the onTransferReceived function.
interface IERC1363Receiver {
function onTransferReceived(
address operator,
address from,
uint256 value,
bytes calldata data
) external returns (bytes4);
}- operator: The address initiating the transfer.
- from: The account debited for the tokens.
- value: The number of tokens transferred.
- data: Additional information provided by the operator.
Implementations must validate that msg.sender is the expected ERC-1363 token contract to prevent malicious calls.
Here’s a minimal example of a token receiver:
contract TokenReceiver is IERC1363Receiver {
address internal erc1363Token;
constructor(address erc1363Token_) {
erc1363Token = erc1363Token_;
}
mapping(address => uint256) public balances;
function onTransferReceived(
address operator,
address from,
uint256 value,
bytes calldata data
) external returns (bytes4) {
require(msg.sender == erc1363Token, "Unexpected token");
balances[from] += value;
return this.onTransferReceived.selector;
}
function withdraw(uint256 value) external {
require(balances[msg.sender] >= value, "Insufficient balance");
balances[msg.sender] -= value;
IERC1363(erc1363Token).transfer(msg.sender, value);
}
}This design eliminates the need for pre-approvals, as transferAndCall both transfers tokens and notifies the contract in one transaction.
Backward Compatibility with ERC-20
A key goal of ERC-1363 is maintaining full backward compatibility with ERC-20. Existing protocols can interact with ERC-1363 tokens exactly as they would with standard ERC-20 tokens. New protocols can optionally use the extended features.
ERC-1363 introduces six new functions to ERC-20:
- Two variants of
transferAndCall - Two variants of
transferFromAndCall - Two variants of
approveAndCall
Each pair includes one function with a data parameter and one without.
function transferAndCall(address to, uint256 value) external returns (bool);
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
function approveAndCall(address spender, uint256 value) external returns (bool);
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);ERC-721 Inspiration: transferFrom vs safeTransferFrom
Similar to ERC-721’s safeTransferFrom, ERC-1363’s transferFromAndCall invokes a hook on the recipient. The term “call” is used instead of “safe” to clarify that the function executes an external call, which introduces reentrancy considerations.
Reference Implementation
The ERC-1363 reference implementation builds on OpenZeppelin’s ERC-20. Below is a simplified overview:
Inheriting from ERC-20
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract ERC1363 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
}Implementing transferFromAndCall
function transferFromAndCall(
address from,
address to,
uint256 value,
bytes memory data
) public virtual returns (bool) {
require(transferFrom(from, to, value), "Transfer failed");
_checkOnTransferReceived(from, to, value, data);
return true;
}The _checkOnTransferReceived Function
This function validates the recipient and invokes the hook:
function _checkOnTransferReceived(
address from,
address to,
uint256 value,
bytes memory data
) private {
require(to.code.length > 0, "EOA cannot receive hooks");
try IERC1363Receiver(to).onTransferReceived(_msgSender(), from, value, data) returns (bytes4 retval) {
require(retval == IERC1363Receiver.onTransferReceived.selector, "Invalid receiver");
} catch (bytes memory reason) {
if (reason.length == 0) revert("ERC1363: receiver reverted");
assembly {
revert(add(32, reason), mload(reason))
}
}
}approveAndCall and IERC1363Spender
The approveAndCall function grants approval and notifies the spender via the onApprovalReceived hook.
interface IERC1363Spender {
function onApprovalReceived(
address owner,
uint256 value,
bytes calldata data
) external returns (bytes4);
}This allows approved spenders to take action immediately after approval.
Example Use Case for Data Parameter
The data parameter enables custom logic in the receiving contract. Below, it specifies a beneficiary for the deposit:
contract ReceiverContract is IERC1363Receiver {
mapping(address => uint256) public deposits;
address immutable token;
constructor(address token_) {
token = token_;
}
function onTransferReceived(
address, // operator
address from,
uint256 value,
bytes memory data
) external returns (bytes4) {
require(msg.sender == token, "Invalid token");
address beneficiary = data.length == 32 ? abi.decode(data, (address)) : from;
deposits[beneficiary] += value;
return this.onTransferReceived.selector;
}
}Historical Context: ERC-223 and ERC-777
Earlier attempts to add transfer hooks to ERC-20 faced compatibility or security challenges:
- ERC-223 (2017): Modified
transferandtransferFromto include hooks, breaking backward compatibility. - ERC-777 (2017): Used a registry for hooks but introduced reentrancy risks and higher gas costs.
ERC-1363 avoids these issues by leaving original ERC-20 functions unchanged and adding new, opt-in functions.
When to Use ERC-1363
ERC-1363 is suitable for any application using ERC-20 tokens. It is particularly beneficial for:
- Reducing transaction costs by combining approval and transfer into one step.
- Enhancing security by minimizing persistent approvals.
- Enabling more complex token interactions in DeFi protocols, gaming, and subscription services.
👉 Explore advanced token standards
Frequently Asked Questions
What is the main advantage of ERC-1363 over ERC-20?
ERC-1363 allows contracts to respond to token transfers automatically, reducing the need for separate approval transactions and improving user experience.
Is ERC-1363 compatible with existing ERC-20 wallets and exchanges?
Yes, because it retains all ERC-20 functions. New features are only activated when using the extended functions.
Can ERC-1363 tokens be sent to regular wallets?
Yes, but only using the standard transfer or transferFrom functions. The hook functions require the recipient to be a contract.
How does ERC-1363 prevent reentrancy attacks?
Developers must implement appropriate checks in hook functions. The standard itself doesn’t enforce reentrancy guards.
What is the purpose of the data parameter?
It allows the sender to pass additional information to the recipient, such as instructions or metadata for processing the transfer.
Are there any gas cost implications?
Using hook functions incurs additional gas due to the external call, but this is offset by eliminating separate approval transactions.