Skip to main content
Change page

ERC-223 Token Standard

Last edit: @corwintines(opens in a new tab), June 11, 2024

Introduction

What is ERC-223?

ERC-223 is a standard for fungible tokens, similar to the ERC-20 standard. The key difference is that ERC-223 defines not only the token API but also the logic for transferring tokens from sender to recipient. It introduces a communication model that allows token transfers to be handled on the recipient's side.

Differences from ERC-20

ERC-223 addresses some limitations of ERC-20 and introduces a new method of interaction between the token contract and a contract that may receive the tokens. There are few things that are possible with ERC-223 but not with ERC-20:

  • Token transfer handling on the recipient's side: Recipients can detect that an ERC-223 token is being deposited.
  • Rejection of improperly sent tokens: If a user sends ERC-223 tokens to a contract not supposed to receive tokens, the contract can reject the transaction, preventing token loss.
  • Metadata in transfers: ERC-223 tokens can include metadata, allowing arbitrary information to be attached to token transactions.

Prerequisites

  • Accounts
  • Smart Contracts
  • Token standards
  • ERC-20

Body

ERC-223 is a token standard that implements an API for tokens within smart contracts. It also declares an API for contracts that are supposed to receive ERC-223 tokens. Contracts that do not support the ERC-223 Receiver API cannot receive ERC-223 tokens, preventing user error.

If a smart contract implements the following methods and events it can be called an ERC-223 compatible token contract. Once deployed, it will be responsible to keep track of the created tokens on Ethereum.

The contract is not obligated to have only these functions and a developer can add any other feature from different token standards to this contract. For example, approve and transferFrom functions are not part of ERC-223 standard but these functions could be implemented should it be necessary.

From EIP-223(opens in a new tab):

Methods

ERC-223 token must implement the following methods:

1function name() public view returns (string)
2function symbol() public view returns (string)
3function decimals() public view returns (uint8)
4function totalSupply() public view returns (uint256)
5function balanceOf(address _owner) public view returns (uint256 balance)
6function transfer(address _to, uint256 _value) public returns (bool success)
7function transfer(address _to, uint256 _value, bytes calldata _data) public returns (bool success)
Copy

A contract that is supposed to receive ERC-223 tokens must implement the following method:

1function tokenReceived(address _from, uint _value, bytes calldata _data)
Copy

If ERC-223 tokens are sent to a contract that doesn't implement the tokenReceived(..) function then the transfer must fail and the tokens must not be moved from the sender's balance.

Events

1event Transfer(address indexed _from, address indexed _to, uint256 _value, bytes calldata _data)
Copy

Examples

The API of ERC-223 token is similar to that of ERC-20, so from UI development point of view there is no difference. The only exception here is that ERC-223 tokens may not have approve + transferFrom functions as these are optional for this standard.

Solidity examples

The following example illustrates how a basic ERC-223 token contract operates:

1pragma solidity ^0.8.19;
2abstract contract IERC223Recipient {
3 function tokenReceived(address _from, uint _value, bytes memory _data) public virtual;
4}
5contract VeryBasicERC223Token {
6 event Transfer(address indexed from, address indexed to, uint value, bytes data);
7 string private _name;
8 string private _symbol;
9 uint8 private _decimals;
10 uint256 private _totalSupply;
11 mapping(address => uint256) private balances;
12 function name() public view returns (string memory) { return _name; }
13 function symbol() public view returns (string memory) {return _symbol; }
14 function decimals() public view returns (uint8) { return _decimals; }
15 function totalSupply() public view returns (uint256) { return _totalSupply; }
16 function balanceOf(address _owner) public view returns (uint256) { return balances[_owner]; }
17 function isContract(address account) internal view returns (bool) {
18 uint256 size;
19 assembly { size := extcodesize(account) }
20 return size > 0;
21 }
22 function transfer(address _to, uint _value, bytes calldata _data) public returns (bool success){
23 balances[msg.sender] = balances[msg.sender] - _value;
24 balances[_to] = balances[_to] + _value;
25 if(isContract(_to)) {
26 IERC223Recipient(_to).tokenReceived(msg.sender, _value, _data);
27 }
28 emit Transfer(msg.sender, _to, _value, _data);
29 return true;
30 }
31 function transfer(address _to, uint _value) public returns (bool success){
32 bytes memory _empty = hex"00000000";
33 balances[msg.sender] = balances[msg.sender] - _value;
34 balances[_to] = balances[_to] + _value;
35 if(isContract(_to)) {
36 IERC223Recipient(_to).tokenReceived(msg.sender, _value, _empty);
37 }
38 emit Transfer(msg.sender, _to, _value, _empty);
39 return true;
40 }
41}
Show all
Copy

Now we want another contract to accept deposits of tokenA assuming that tokenA is an ERC-223 token. The contract must accept only tokenA and reject any other tokens. When the contract receives tokenA it must emit a Deposit() event and increase the value of the internal deposits variable.

Here is the code:

1contract RecipientContract is IERC223Recipient {
2 event Deposit(address whoSentTheTokens);
3 uint256 deposits = 0;
4 address tokenA; // The only token that we want to accept.
5 function tokenReceived(address _from, uint _value, bytes memory _data) public override
6 {
7 // It is important to understand that within this function
8 // msg.sender is the address of a token that is being received,
9 // msg.value is always 0 as the token contract does not own or send Ether in most cases,
10 // _from is the sender of the token transfer,
11 // _value is the amount of tokens that was deposited.
12 require(msg.sender == tokenA);
13 deposits += _value;
14 emit Deposit(_from);
15 }
16}
Show all
Copy

Frequently asked questions

What will happen if we send some tokenB to the contract?

The transaction will fail, and the transfer of tokens will not happen. The tokens will be returned to the sender's address.

How can we make a deposit to this contract?

Call the transfer(address,uint256) or transfer(address,uint256,bytes) function of the ERC-223 token, specifying the address of the RecipientContract.

What will happen if we transfer an ERC-20 token to this contract?

If an ERC-20 token is sent to the RecipientContract, the tokens will be transferred, but the transfer will not be recognized (no Deposit() event will be fired, and the deposits value will not change). Unwanted ERC-20 deposits cannot be filtered or prevented.

What if we want to execute some function after the token deposit is completed?

There are multiple ways of doing so. In this example we will follow the method which makes ERC-223 transfers identical to Ether transfers:

1contract RecipientContract is IERC223Recipient {
2 event Foo();
3 event Bar(uint256 someNumber);
4 address tokenA; // The only token that we want to accept.
5 function tokenReceived(address _from, uint _value, bytes memory _data) public override
6 {
7 require(msg.sender == tokenA);
8 address(this).call(_data); // Handle incoming transaction and perform a subsequent function call.
9 }
10 function foo() public
11 {
12 emit Foo();
13 }
14 function bar(uint256 _someNumber) public
15 {
16 emit Bar(_someNumber);
17 }
18}
Show all
Copy

When the RecipientContract will receive a ERC-223 token the contract will execute a function encoded as _data parameter of the token transaction, identical to how Ether transactions encode function calls as transaction data. Read the data field(opens in a new tab) for more information.

In the above example an ERC-223 token must be transferred to the address of the RecipientContract with the transfer(address,uin256,bytes calldata _data) function. If the data parameter will be 0xc2985578 (the signature of a foo() function) then the function foo() will be invoked after the token deposit is received and the event Foo() will be fired.

Parameters can be encoded in the data of the token transfer as well, for example we can call the bar() function with 12345 value for _someNumber. In this case the data must be 0x0423a13200000000000000000000000000000000000000000000000000000000000004d2 where 0x0423a132 is the signature of the bar(uint256) function and 00000000000000000000000000000000000000000000000000000000000004d2 is 12345 as uint256.

Limitations

While ERC-223 addresses several issues found in the ERC-20 standard, it is not without its own limitations:

  • Adoption and Compatibility: ERC-223 is not yet widely adopted, which may limit its compatibility with existing tools and platforms.
  • Backward Compatibility: ERC-223 is not backward compatible with ERC-20, meaning that existing ERC-20 contracts and tools will not work with ERC-223 tokens without modifications.
  • Gas Costs: The additional checks and functionalities in ERC-223 transfers may result in higher gas costs compared to ERC-20 transactions.

Further reading

Was this article helpful?

Website last updated: June 19, 2024

Learn

  • Learn Hub
  • What is Ethereum?
  • What is ether (ETH)?
  • Ethereum wallets
  • What is Web3?
  • Smart contracts
  • Gas fees
  • Run a node
  • Ethereum security and scam prevention
  • Quiz Hub
  • Ethereum glossary
(opens in a new tab)(opens in a new tab)(opens in a new tab)
  • About us
  • Ethereum brand assets
  • Code of conduct
  • Jobs
  • Privacy policy
  • Terms of use
  • Cookie policy
  • Press Contact(opens in a new tab)