In this article I’ll cover the process of a ERC-223 token creation. The token can be created on any EVM-compatible chain such as Ethereum, Arbitrum, Optimism and many more.
You will need:
- Browser
- Wallet
- Native currency (ETH on Ethereum) to cover the gas fee
- IDE, in this example I’m using Remix
What is an ERC-223?
ERC-223 is a token standard on Ethereum.
ERC-223 standard defines the technical details and logic that a token must implement to make all ERC-223 compliant tokens similar to each other. ERC-223 standard was developed to fix a security vulnerability in ERC-20 standard that caused $90,000,000 to $200,000,000 financial damage to the users of Ethereum tokens during 2023.
You can find more ERC-223 resources and the history of Ethereum token standards here: https://dexaran.github.io/erc223/
Why should I use ERC-223?
- A lot of funds were lost because of ERC-20 security flaws. If you do worry about the security of your users funds — then you shouldn’t use ERC-20. ERC-223 is the closest analogue designed with security in mind.
- EIP-7417 makes ERC-20 and ERC-223 tokens interchangeable. Anyone can convert ERC-20 tokens to ERC-223 or vice versa using the Token Converter service.
- ERC-223 makes tokens behave identical to Ether with no extra requirements, no extra code and therefore less chances to introduce a new problem. Ether transferring logic is minimalistic, clean and completely sufficient to fulfill smart-contract developers needs.
- ERC-223 is the most debated proposal in the history of Ethereum. All it’s caveats are thoroughly researched and fixed.
- The logic of depositing tokens to a contract is the key difference between ERC-20 and ERC-223. In case of ERC-20 a user must authorize the contract to pull tokens and then execute a function that will pull them. This is a couple of two different transactions and the mechanic of authorizing some third party to manipulate users funds is not the best idea in a trustless world of decentralization. In case of ERC-223 the user must simply send tokens to the destination contract and the deposit will be executed on the recipients side in exactly the same way as it’s done with Ether deposits.
Key Points about ERC-223 Tokens
The source codes of the reference ERC-223 token can be obtained here: https://github.com/Dexaran/ERC223-token-standard/
Any ERC-223 compliant token MUST implement the following functions:
Total Supply
function totalSupply() view returns (uint256)
A method that defines the total supply of your tokens. It is important to note that this function may contain token-specific logic that excludes some tokens from the supply if they were “burned” in some address on purpose. For example 0xdead000000000000000000000000000000000000.
Balance Of
function balanceOf(address _owner) view returns (uint256)
A method that returns the number of tokens an address has.
Transfer
function transfer(address _to, uint _value) returns (bool)
function transfer(address _to, uint _value, bytes calldata _data) returns (bool)
This two transfer functions move tokens from the balance of the user who initiated the transaction to the _to address. The function without _data parameter is there for the sake of backwards compatibility with ERC-20 transfer function i.e. for any wallet that operated with ERC-20 tokens sending ERC-223 would be the same.
It is important to note that both transfer
functions are notifying the recipient if it is a contract and therefore the recipient can handle an incoming ERC-223 token transaction. For example the recipient can accept only specified ERC-223 token and reject anything else. With ERC-20 it was not possible to reject a wrong token.
In addition to these mandatory functions it is recommended to implement optional functions to improve the token operability.
Name, Symbol, Decimals
function name() view returns (string memory)
function symbol() view returns (string memory)
function decimals() view returns (uint8)
This functions are inherited from ERC-20. Name
displays the name of the tokens, for example “Dex223 token”. Symbol
displays a ticker of the token. Most exchanges allow 5-symbol long tickers, for example “D223”.
Decimals
is a special variable that would be used by wallets to display tokens. There are no fractional numbers in Solidity so in order to display a fraction of a token developers define its decimals and the UIs divide the actual amount of tokens stored in the contract by 10^decimals
. For example USDT has 6 decimals. If a user has 2381000 USDT returned by the balanceOf
function then the wallet would display 2.381 USDT balance.
Standard
function standard() public view returns (string memory)
Even though this function is optional it is strongly recommended to implement it in production. It would make much simplier for wallets and services that interact with tokens to determine whether a token is ERC-20 or ERC-223 without diving in the tokens logic.
ERC-20 and ERC-223 tokens can have identical set of functions which makes it difficult to differentiate for UIs. The main difference is the internal logic of the transfer
function.
ERC-223 standard
function must return a single “223” string.
approve & transferFrom
function approve(address spender, uint256 value) external returns (bool)
function transferFrom(address from, address to, uint256 value) external returns (bool)
Even though this functions are not part of the ERC-223 standard and these are not necessary for a ERC-223 token operability they can be added to the token contract to make it fully compatible with services that are designed for ERC-20 tokens.
It should be noted however that approve & transferFrom
is a legacy transferring method that was introduced in 2015 in ERC-20 just to address a bug in the early-days EVM that doesn’t exist since 2017. There is no actual reason to rely on this transferring methods in trustless systems currently.
Writing the Smart Contract
Open Remix or your IDE and create a new contract.
You can copy the code from the reference implementation in EIP-223, my reference implementation or just use the code below:
pragma solidity ^0.8.0;
abstract contract IERC223Recipient {
function tokenReceived(address _from, uint _value, bytes memory _data) public virtual returns (bytes4) { return 0x8943ec02; }
}
library Address {
function isContract(address account) internal view returns (bool) {
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
}
contract ERC223Token {
event Transfer(address indexed from, address indexed to, uint value, bytes data);
string private _name = "My ERC-223 Token";
string private _symbol = "MTKN";
uint8 private _decimals = 18;
uint256 private _totalSupply;
mapping(address => uint256) private balances;
constructor()
{
balances[msg.sender] = 100 * 10 ** _decimals;
_totalSupply = 100 * 10 ** _decimals;
emit Transfer(address(0), msg.sender, 100 * 10 ** _decimals, hex"000000");
}
function name() public view returns (string memory) { return _name; }
function symbol() public view returns (string memory) { return _symbol; }
function decimals() public view returns (uint8) { return _decimals; }
function standard() public view returns (string memory) { return "223"; }
function totalSupply() public view returns (uint256) { return _totalSupply; }
function balanceOf(address _owner) public view returns (uint256) { return balances[_owner]; }
function transfer(address _to, uint _value, bytes calldata _data) public returns (bool success)
{
balances[msg.sender] = balances[msg.sender] - _value;
balances[_to] = balances[_to] + _value;
if(Address.isContract(_to)) {
IERC223Recipient(_to).tokenReceived(msg.sender, _value, _data);
}
emit Transfer(msg.sender, _to, _value, _data);
return true;
}
function transfer(address _to, uint _value) public returns (bool success)
{
bytes memory _empty = hex"00000000";
balances[msg.sender] = balances[msg.sender] - _value;
balances[_to] = balances[_to] + _value;
if(Address.isContract(_to)) {
IERC223Recipient(_to).tokenReceived(msg.sender, _value, _empty);
}
emit Transfer(msg.sender, _to, _value, _empty);
return true;
}
}
First we will need two helper contracts:
- IERC223Recipient. This is an interface contract that defines a
tokenReceived
function. Any contract that wants to accept ERC-223 tokens must implement IERC223Recipient, otherwise the tokens will be automatically rejected and there will be no way to deposit them to the contract viatransfer
function. - Address library. This library is needed to determine if the recipient of the ERC-223 transfer is a contract or not. If there is a bytecode on the recipient’s address — we consider it a contract.
Then the logic of the ERC-223 Token should be implemented. In our case the contract will be assigned a “My ERC-223 Token” name
, “MTKN” symbol
and 18 decimals
(default for most tokens on Ethereum).
constructor()
{
balances[msg.sender] = 100 * 10 ** _decimals;
_totalSupply = 100 * 10 ** _decimals;
emit Transfer(address(0), msg.sender, 100 * 10 ** _decimals, hex"000000");
}
This lines will create 100 tokens and assign to the balance of the sender of the transaction.
And event Transfer(address(0), msg.sender, 100 * 10 ** _decimals, hex”000000")
indicates the creation of the tokens as if they were sent from 0x0 address.
Deploying the contract with MetaMask
Get MetaMask if you don’t already have it.
Go to the Deploy & Run Transactions tab. For deployment, use the Injected Provider option under the Environment.
Before the deployment, ensure that your MetaMask is set to the correct network and ERC223Token contract is the selected contract to be deployed.
Finally, click on the Deploy button to deploy your contract. Confirm the transaction in MetaMask.
Great job! Your token is deployed.
Deploying the contract with bytecode
Compile the contract.
Make sure to switch to the ERC223Token (ERC223Token.sol) after the compilation is completed (Address would be displayed by default as Remix sorts all the compiled contracts in alphabetic order).
Click on copy the bytecode.
Paste your bytecode to your wallet that allows you to deploy the contract bytecode. Send the transaction and wait for it to confirm.
Congratulations! You’ve just created your ERC-223 token.
Example ERC-223 Token
Here is how a ERC-223 token looks like on Ethereum mainnet: https://etherscan.io/address/0xcce968120e6ded56f32fbfe5a2ec06cbf1e7c8ed