A Gentle Introduction to ERC Standards: ERC-20, ERC-721, and ERC-1155

A blog post covering some of the most common ERC token standards used for creating fungible, non-fungible, and semi-fungible tokens.

Hi, fren! gm. β˜€οΈ

This blog post will cover some of the most common ERC token standards you can use to create fungible, non-fungible, and semi-fungible tokens.

So, without further ado, let's get started!

D_D Newsletter CTA

First of All, WTH is ERC? πŸ€”

ERC stands for "Ethereum Request for Comments," a proposal document developers write. It is then reviewed by the Ethereum community using a process called "Ethereum Improvement Proposal" or EIP.

What is ERC-20?

ERC-20 is the 20th proposal, which Fabian Vogelsteller proposed in 2015, and Ethereum integrated it in 2017. It is the most commonly used token standard in the Ethereum ecosystem.

The tokens created using the ERC-20 standard have three properties:

πŸ”Ή They are Fungible: You can use all tokens interchangeably, and each token holds the same value. Think of a Dollar bill πŸ’΅

πŸ”Ή They are transferrable: You can send the tokens from one address to another.

πŸ”Ή They have a fixed supply.

The Need for ERC-20

Back in the day, when everyone was trying to come up with their own fungible tokens, there were no set rules or guidelines for developing tokens. So, everyone had to reinvent the wheel to create their tokens.

As a result, all the tokens were different from each other. This mismatch was extremely painful for developers to work with other tokens, and for exchanges and wallets to list and handle additional tokens on their platforms.

The ERC-20 introduced a standard (or a set of rules) that allowed developers to create their own fungible tokens on the Ethereum blockchain. It laid out guidelines that all Ethereum-based tokens must adhere to.

Today, wallets like Metamask and exchanges like Binance use the ERC-20 standard to list various standardized tokens on their platforms and handle the exchange of value between them.

The Technical Aspects

ERC-20 provides a standard interface for fungible tokens.

An interface defines rules that a program must follow.

In this case, it contains a set of functions and events that a smart contract must implement. While an interface doesn't specify how you should implement the functions or events, it defines which you should implement functions or events.

The ERC-20 standard defines three optional and six mandatory functions that a smart contract should implement to create a fungible token.

The three optional ones are:

πŸ”Ή name(): returns the token's name - e.g., ShurikenToken.

function name() public view returns (string)

πŸ”Ή symbol(): returns the token's symbol - e.g., CST.

function symbol() public view returns (string)

πŸ”Ή decimals(): returns the number of decimals places the token usesβ€”more on decimals here.

function decimals() public view returns (uint8)

The six mandatory functions are:

πŸ”Ή totalSupply(): determines the total supply of tokens. Once the tokens reach this limit, the smart contract will refuse to mint new tokens.

function totalSupply() public view returns (uint256)

πŸ”Ή balanceOf(): takes in the _owner address parameter and returns the number of tokens a given address holds.

function balanceOf(address _owner) public view returns (uint256 balance)

πŸ”Ή transfer(): transfers a certain number of tokens from the total supply to a user address.

function transfer(address _to, uint256 _value) public returns (bool success)

πŸ”Ή transferFrom(): allows users to transfer tokens from one address to another.

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)

πŸ”Ή approve(): verifies whether a smart contract is allowed to allocate a certain amount of tokens to a user considering the total supply, making sure that there are no extra or missing tokens.

function approve(address _spender, uint256 _value) public returns (bool success)

πŸ”Ή allowance(): how much an address is allowed to spend. Essentially it checks whether a user has enough balance to transact with another user.

function allowance(address _owner, address _spender) public view returns (uint256 remaining)

Apart from these functions, the smart contract should also emit two events:

πŸ”Ή Transfer: the contract emits this event when tokens are transferred from one wallet to another. It gives the address of both the sender and recipient, as well as the number of tokens transferred.

event Transfer(address indexed from, address indexed to, uint256 value);

πŸ”Ή Approval: the contract emits this event when the approve() function is successfully executed.

event Approval(address indexed _owner, address indexed _spender, uint256 _value)

ℹ️ If you're unfamiliar with Events in Solidity, here's a fantastic blog post by Patrick Collins on the Chainlink blog.

The ERC-20 token standard provides a basic structure for the creation of fungible tokens in a uniform manner. It takes out the complexities the "reinventing the wheel"-approach introduced in the earlier days.

Now that the significance of ERC-20 is clear let's move on to another common token standard that powered the world of something we know as NFTs.

What is ERC-721?

While the ERC-20 token standard caters to creating fungible tokens, ERC-721 is the token standard for creating non-fungible tokens (NFTs). It was proposed by William Entriken, Dieter Shirley, Jacob Evans, and Nastassia Sachs from the CryptoKitties team in 2018.

ERC-721 defines a minimum interface a smart contract must implement to allow unique tokens to be managed, owned, and traded.

Unlike the ERC-20 standard, where no concept of uniqueness exists, all tokens created using the ERC-721 standard are non-fungible or unique. Each NFT is linked to its owner and possesses metadata and a unique tokenId.

ERC20 vs ERC721 | Image Credit: ERC721.org

Image Credit: ERC721.org

The use cases for the ERC-721 can be both digital & physical ownership. We can own digital assets like digital art, music NFTs, digital land like Decentraland, and physical assets like tokenized house deeds & non-digital art.

The ERC-721 Functions and Events

The ERC-721 standard has the following functions that a smart contract should implement:

πŸ”Ή ownerOf(): takes in a _tokenId and returns the address of the owner of the NFT with that particular _tokenId.

function ownerOf(uint256 _tokenId) external view returns (address);

πŸ”Ή transferFrom(): transfers the ownership of an NFT from one address ( _from) to another ( _to).

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

πŸ”Ή safeTransferFrom(): transfer the ownership of an NFT from one address to another. It also checks if an address receiving the NFT is an ERC-721 receiver (i.e., whether it can receive the token or not), ensuring that the tokens aren't lost permanently.

function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

πŸ”Ή approve(): approves another entity's permission to transfer an NFT on behalf of the owner. This approval allows NFT platforms like OpenSea to transfer ownership of NFTs you listed for sale when someone buys it.

function approve(address _approved, uint256 _tokenId) external payable;

πŸ”Ή setApprovalForAll(): enables or disables approval for a third party to manage all NFTs of a caller (i.e., msg.sender).

function setApprovalForAll(address _operator, bool _approved) external;

πŸ”Ή getApproved(): takes in a _tokenID and gets the approved address for a single NFT with that particular ID.

function getApproved(uint256 _tokenId) external view returns (address);

πŸ”Ή isApprovedForAll(): queries if an address is an authorized operator for another address and returns true or false given the approval status of the operator.

function isApprovedForAll(address _owner, address _operator) external view returns (bool);

ERC-20 compatible functions:

πŸ”Ή name() and symbol(): used to define the name & symbol of a token. These optional functions help in providing compatibility with the ERC-20 standard and make it easier for existing wallets to display information about the token.

function name() external view returns (string _name);
function symbol() external view returns (string _symbol);

πŸ”Ή totalSupply(): determines the total supply of tokens. In the case of ERC-721, the entire supply doesn't necessarily have to be a constant.

function totalSupply() external view returns (uint256);

πŸ”Ή balanceOf(): returns the count of all the NFTs a given address owns.

function balanceOf(address _owner) external view returns (uint256);

Apart from these functions, the smart contract should also emit the following events:

πŸ”Ή Transfer: The contract emits this event when the ownership of an NFT changes or you mint a new NFT.

event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

πŸ”Ή Approval: The contract emits this event when the approved address for an NFT is changed (whenever approve() function is executed).

event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

πŸ”Ή ApprovalForAll: the contract emits this event when an operator is enabled or disabled for an owner. This operator can manage all NFTs of an owner.

event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

While the ERC-721 token standard allows us to create unique non-fungible tokens, it has some shortcomings.

One key issue is that since all the tokens are unique, each token needs its own transaction to change hands. You can't just transfer 10 NFTs in a single transaction like you would with ERC-20 tokens. As a result, it makes the entire process expensive.

Also, what if you want only some of your tokens to be unique? πŸ€”

Think of an FPS game. Let's say the game has different types of unique skins for weapons: swords, guns, throwables, etc., and an in-game currency called NinjaCoins. A user owns one gun, ten swords, and 10,000 NinjaCoins.

Now, if we work with the ERC-721 standard, we have to write a different smart contract for each unique item or NFT, plus we have to write another smart contract for our NinjaCoins (that will be an ERC-20 token since it's fungible).

What if a single token standard could facilitate the creation of fungible and non-fungible tokens in a single deployed smart contract?

Enter ERC-1155! 😎

Best of Both Worlds

What is ERC-1155?

Witek Radomski & the Enjin team introduced the ERC-1155 token standard in 2018.

The ERC-1155 token standard combines the abilities of ERC-20 and ERC-721 and tackles their shortcomings by providing a standard interface for smart contracts to manage multiple token types.

Some key features of the ERC-1155 token standard are:

  • πŸ›  Ability to create fungible, non-fungible, and semi-fungible tokens with a single, smart contract.
  • πŸ”€ Ability to do batch transfers: sending multiple tokens in a single-transactions.
  • ⛽️ Gas efficient.
  • πŸ“¦ Requires less storage space.

One key difference between the ERC-20, ERC-721, and ERC-1155 is:

πŸ”Ή The ERC-20 maps different addresses to the number of tokens they hold,

πŸ”Ή The ERC-721 maps unique token IDs to owners, and

πŸ”Ή The ERC-1155 has a nested mapping of IDs to owners to amount of tokens they own.

ERC20 vs ERC721 vs ERC1155 Image Credit: OpenSea blog

The ERC-1155 Functions and Events

The ERC-1155 token standard has the following functions that a smart contract should implement:

πŸ”Ή safeTransferFrom(): transfers _value amount of an _id from one address ( _from) to another ( _to).

function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;

πŸ”Ή safeBatchTransferFrom(): allows the batch transfer of tokens. This function works similarly to the transferFrom() function in the ERC-20 standard. The only difference being we pass the _values and _ids as an array.

// ERC-20
function transferFrom(address from, address to, uint256 value) external returns (bool);

// ERC-1155
function safeBatchTransferFrom(
    address _from,
    address _to,
    uint256[] calldata _ids,
    uint256[] calldata _values,
    bytes calldata _data
) external;

For example, let's say we want to transfer ten swords, five guns, and 100 NinjaCoins to an address. The IDs for swords, guns, and NinjaCoins are 2, 3, and 4, respectively.

So, _ids = [2, 3, 4] and _values = [10, 5, 100]

So, the resulting transfer with the safeBatchTransferFrom() function would be:

Transfer ten tokens with ID 2 from _from to _to. Transfer five tokens with ID 3 from _from to _to. Transfer a hundred tokens with ID 4 from _from to _to.

πŸ”Ή balanceOf(): takes in an _owner and _id and gets the balance of an account's tokens of that respective token id.

function balanceOf(address _owner, uint256 _id) external view returns (uint256);

πŸ”Ή balanceOfBatch(): takes in an array of _owners addresses and _ids, and gets the balance of 'each' owner/id pair.

function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory);

For example, given _ids = [2, 3, 4] and _owners = [0xd1dc..., 0x7c2d..., 0x1bdc...], the return value will be:

[
    balanceOf(0xd1dc...),
    balanceOf(0x7c2d...),
    balanceOf(0x1bdc...)
]

πŸ”Ή setApprovalForAll(): enables or disables approval for a third party ("operator") to manage all of the caller's tokens.

function setApprovalForAll(address _operator, bool _approved) external;

It returns true if the operator is approved, else false.

πŸ’‘ The approvals in ERC-1155 are slightly different than ERC-20. Instead of approving specific amounts, you set an operator to approved or not approved via setApprovalForAll().

πŸ”Ή isApprovedForAll(): queries the current approval status of an operator for a given owner and returns true or false given the approval status of the operator. Similar to the isApprovedForAll() function in the ERC-721 standard.

function isApprovedForAll(address _owner, address _operator) external view returns (bool);

Apart from these functions, the smart contract should also emit the following events:

πŸ”Ή TransferSingle & TransferBatch: the contract emits either of these events when tokens owners change (i.e., when safeTransferFrom() or safeBatchTransferFrom() functions are executed).

event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value);
event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values);

πŸ”Ή ApprovalForAll: the contract emits this event when approval for a second party or operator address to manage all tokens for an owner address is enabled or disabled (i.e., when the setApprovalForAll() function is executed).

event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

πŸ”Ή URI: must be emitted when the URI for a token ID is updated.

event URI(string _value, uint256 indexed _id);

Conclusion

That's it for now, fren!

This blog post has covered a lot about the three most common ERC token standards.

We learned how the ERC-20 standard provides a basic structure for the creation of fungible tokens in a uniform manner, and how ERC-721 tackled the shortcomings & inefficiencies of the ERC-20 standard by providing a standard interface for creating & managing non-fungible tokens.

And then how the ERC-1155 standard combined the abilities of ERC-20 and ERC-721 by tackling their shortcomings, such as storage redundancy and gas inefficiency, providing a "best of both worlds" experience.

But the ERC-1155 standard still needs improvement. The ever-evolving nature of the Ethereum ecosystem demands improvements in token standards as new advancements are made.


Prefer a TL;DR Thread?

I've got you covered! πŸ™ŒπŸ»

Here's a TL;DR version of this blog post. While you're at it, please consider following me @cryptoshuriken for more such content.

Twitter Thread Image: A thread on ERC Token Standards - CryptoShuriken


D_D Newsletter CTA


If you enjoyed reading this article, please consider the following:

#WAGMI ✌🏻


Originally published at https://cryptoshuriken.com