Ethereum ERC Standards You Should Know About

The ERC20 token standard has achieved near-complete industry adoption. It defined six minimal requirements for the way tokens behave on the Ethereum blockchain. Anyone could comply with the token standard and implement additional functions as needed. This standard ignited the ICO wave, allowing for the creation of core infrastructure and exchanges that is the backbone of the crypto ecosystem today.

Let’s look at Ethereum ERC standards you should know about - including some you’ve never seen before!

📬 Get updates straight to your inbox.

Subscribe to my newsletter so you don't miss new content.

What’s an ERC?

An ERC (Ethereum Request for Comment) decribes application-level blueprints and conventions in the Ethereum ecosystem. This includes smart contract standards such as token standards (ERC20) and are usually accompanied by a reference implementation. The ERC authors are responsible for building consensus within the Ethereum community for it to be accepted. Once peer reviewed and vetted by the developer community, the proposal becomes a standard.

You can see a list of recent ERCs here. In the following sections, we’ll examine several categories of ERCs: Token Standards, Pseudo-Introspection, Decentralized Identity, Recurring Payments, and Meta Transactions.

Token Standards

ERC20 Token Standard

The ERC20 token standard is a set of functions that an Ethereum-based token has to implement. The majority of tokens on exchanges are ERC20 tokens. ERC20 tokens implement the following interface:

interface ERC20 {
    function totalSupply() public view returns (uint);
    function balanceOf(address tokenOwner) public view returns (uint balance);
    function transfer(address to, uint tokens) public returns (bool success);
    function approve(address spender, uint tokens) public returns (bool success);
    function allowance(address tokenOwner, address spender) public view returns (uint remaining);
    function transferFrom(address from, address to, uint tokens) public returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

The functions are:

  • totalSupply: Returns the total circulating amount of tokens.

  • balanceOf: Returns how many tokens exist in an account.

  • transfer: Transfer an amount of tokens from token owner’s account to another account.

  • approve: A token owner can approve for spender to transferFrom tokens from the token owner’s account.

  • allowance: Returns the amount of tokens approved by the owner that can transferred to the spender’s account.

  • transferFrom: Allows a spender account to transfer an amount of tokens on behalf of the token owner to another account.

ERC20 remains the most widely adopted standard in the Ethereum ecosystem.

ERC223

Solidity smart contracts can listen to and react to Ether transfers using the payable fallback function like so:

contract Sink {
    function() external payable {
       // Do something with msg.value, when Ether is sent to this contract
    }
}

However, you can’t do the same for ERC20 tokens. A major limitation of the ERC20 token standard is the lack of a way to ‘react’ to ERC20 transfer events. This results in ERC20 tokens forever trapped in contracts when users accidentally sent tokens to the wrong address.

ERC223 is a superset of the ERC20 token standard. It allows the use of tokens as first class value transfer assets in smart contract development by extending the token transfer function to check the destination address explicitly accepts tokens:

contract ERC223 {
  function transfer(address to, uint value, bytes data) {
        uint codeLength;
        assembly {
          codeLength := extcodesize(to)
        }

        balances[msg.sender] = balances[msg.sender].sub(value);
        balances[to] = balances[to].add(value);
        if(codeLength>0) {
            // Require proper transaction handling.
            ERC223Receiver receiver = ERC223Receiver(to);
            receiver.tokenFallback(msg.sender, value, data);
        }
    }
}

ERC223 disallows token transfers to contracts that don’t support token receiving and handling. It requires contracts to implement the ERC223Receiver interface in order to receive tokens. If a user tries to send ERC223 tokens to a non-receiver contract the function will throw in the same way that it would if you sent Ether to a contract without the called function being payable.

contract ExampleReceiver is StandardReceiver {
  function () tokenPayable {
    LogTokenPayable(tkn.addr, tkn.sender, tkn.value);
  }

  event LogTokenPayable(address token, address sender, uint value);
}

In the above snippet, functions have a tokenPayable modifier and inside the functions you have access to the tkn struct that tries to mimic the msg struct used for ether calls.

ERC721 Non-Fungible Token Standard

ERC20 tokens are fungible, meaning that a balance of tokens in one account and tokens in another are interchangeable. Every ERC20 token is the same as every other ERC20 token.

In contrast, Non-fungible tokens (NFT) are all unique, a property used to create scarce digital assets called cryptocollectibles. You can think of ERC20 as the token type for things that are currency (paper bills) and ERC721 as the token type for collectibles (Pokemon cards, beanie babies.)

Cryptokitties is the most well-known example of an ERC721 token. CryptoKitties are unique collectible digital artworks of cats. You can own and breed the cats to create new CryptoKitties.

The ERC721 token standard defines a minimum interface a smart contract must implement to allow cryptocollectibles to be managed, owned, and traded. An ERC721 token implements the following interface:

contract IERC721 is IERC165 {
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    function balanceOf(address owner) public view returns (uint256 balance);
    function ownerOf(uint256 tokenId) public view returns (address owner);

    function approve(address to, uint256 tokenId) public;
    function getApproved(uint256 tokenId) public view returns (address operator);

    function setApprovalForAll(address operator, bool approved) public;
    function isApprovedForAll(address owner, address operator) public view returns (bool);

    function transferFrom(address from, address to, uint256 tokenId) public;
    function safeTransferFrom(address from, address to, uint256 tokenId) public;

    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public;
}

Users buy, sell, and discover cryptocollectibles on marketplaces that build on the ERC721 standard.

ERC777 Token Standard

ERC777 is a new token standard that aims to address the limitations of ERC20, while remaining backwards compatible. It defines advanced features to interact with tokens. Namely, operators to send tokens on behalf of another address and send/receive hooks to offer token holders more control over their tokens.

It takes advantage of ERC1820 to find out whether and where to notify contracts and regular addresses when they receive tokens as well as to allow compatibility with already-deployed contracts.

Features that sets ERC777 apart include:

  • Both contracts and regular addresses can control and reject which token they send by registering a tokensToSend hook.
  • Both contracts and regular addresses can control and reject which token they receive by registering a tokensReceived hook.
  • The tokensReceived hook allows to send tokens to a contract and notify it in a single transaction, unlike ERC20 which require a double call (approve/transferFrom) to achieve this.
  • The token holder can “authorize” and “revoke” operators which can send tokens on their behalf. These operators are intended to be verified contracts such as an exchange, a cheque processor or an automatic charging system.
  • Every token transaction contains data and operatorData bytes fields to be used freely to pass data from the token holder and the operator, respectively.
interface ERC777Token {
    function name() external view returns (string);
    function symbol() external view returns (string);
    function totalSupply() external view returns (uint256);
    function balanceOf(address owner) external view returns (uint256);
    function granularity() external view returns (uint256);

    function defaultOperators() external view returns (address[]);
    function authorizeOperator(address operator) external;
    function revokeOperator(address operator) external;
    function isOperatorFor(address operator, address tokenHolder) external view returns (bool);

    function send(address to, uint256 amount, bytes data) external;
    function operatorSend(address from, address to, uint256 amount, bytes data, bytes operatorData) external;

    function burn(uint256 amount, bytes data) external;
    function operatorBurn(address from, uint256 amount, bytes data, bytes operatorData) external;

    event Sent(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256 amount,
        bytes data,
        bytes operatorData
    );
    event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData);
    event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData);
    event AuthorizedOperator(address indexed operator, address indexed tokenHolder);
    event RevokedOperator(address indexed operator, address indexed tokenHolder);
}

interface ERC777TokensSender {
    function tokensToSend(
        address operator,
        address from,
        address to,
        uint256 amount,
        bytes data,
        bytes operatorData
    ) external;
}

interface ERC777TokensRecipient {
    function tokensReceived(
        address operator,
        address from,
        address to,
        uint256 amount,
        bytes data,
        bytes operatorData
    ) external;
}

ERC1155 Multi Token Standard

Tokens standards like ERC20 and ERC721 require a separate contract to be deployed for each token type or collection. This places a lot of redundant bytecode on the Ethereum blockchain and limits certain functionality by separating each token contract into its own permissioned address. With the rise of blockchain games that uses NFTs as in-game items, the explosion of contracts would introduce performance and cost problems.

ERC1155 is a standard for contracts that manage multiple token types. A single deployed contract may include any combination of fungible tokens, non-fungible tokens, or other configurations.

With ERC1155, you can transfer multiple token types at once, saving on transaction costs. Trading of multiple tokens can be built on top of this standard and it removes the need to approve individual token contracts separately. It’s also easy to describe and mix multiple fungible or non-fungible token types in a single contract.

Security Token Standards

Security tokens are cryptographic tokens that represent fractional ownership in an entity, pay dividends, and share profits to its token holders. On the Ethereum blockchain, several competing standards such as ERC1400 and R-Token are in development.

Learn more about the latest security token standards here.

Pseudo-Introspection

This section highlights different approaches to define pseudo-introspection in Ethereum.

ERC165 Standard Interface Detection

It’s sometimes useful to query whether a contract supports an interface in order to adapt the way in which the contract is to be interacted with. ERC165 standardizes the concept and identification of contract interfaces.

According to this standard, an interface is a set of function selectors as defined by the Ethereum ABI. The interface identifier is the XOR of all function selectors in the interface. This code snippet shows how to calculate the interface identifier:

interface Solidity101 {
    function hello() external pure;
    function world(int) external pure;
}

contract Selector {
    function calculateSelector() public pure returns (bytes4) {
        Solidity101 i;
        return i.hello.selector ^ i.world.selector; // XOR of function selectors
    }
}

A contract that is compliant with ERC-165 implements the following interface:

interface ERC165 {
    /// @notice Query if a contract implements an interface
    /// @param interfaceID The interface identifier, as specified in ERC-165
    /// @return `true` if the contract implements `interfaceID` and
    ///  `interfaceID` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

Therefore the implementing contract will have a supportsInterface function that returns:

  • true when interfaceID is 0x01ffc9a7 (EIP165 interface)
  • false when interfaceID is 0xffffffff
  • true for any other interfaceID this contract implements (e.g. Solidity101)
  • false for any other interfaceID

ERC1820 Pseudo-introspection Registry Contract

The ERC1820 standard defines a universal registry smart contract where any address can register which interface it supports and which smart contract is responsible for its implementation. Anyone can query this registry to ask if a specific address implements a given interface and which smart contract handles its implementation.

Similar to ERC165, in ERC1820, ‘implementation’ contracts must implement the following interface:

/// @dev The interface a contract MUST implement if it is the implementer of
/// some (other) interface for any address other than itself.
interface ERC1820ImplementerInterface {
    /// @notice Indicates whether the contract implements the interface 'interfaceHash' for the address 'addr' or not.
    /// @param interfaceHash keccak256 hash of the name of the interface
    /// @param addr Address for which the contract will implement the interface
    /// @return ERC1820_ACCEPT_MAGIC only if the contract implements 'interfaceHash' for the address 'addr'.
    function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32);
}

Implementation addresses are then stored in a registry contract:

contract ERC1820Registry {
    /// @notice mapping from addresses and interface hashes to their implementers.
    mapping(address => mapping(bytes32 => address)) internal interfaces;
    /// @notice mapping from addresses to their manager.
    mapping(address => address) internal managers;
    /// @notice flag for each address and erc165 interface to indicate if it is cached.
    mapping(address => mapping(bytes4 => bool)) internal erc165Cached;

    /// @notice Indicates a contract is the 'implementer' of 'interfaceHash' for 'addr'.
    event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);
    /// @notice Indicates 'newManager' is the address of the new manager for 'addr'.
    event ManagerChanged(address indexed addr, address indexed newManager);

    /// @notice Query if an address implements an interface and through which contract.
    function getInterfaceImplementer(address addr, bytes32 interfaceHash) external view returns (address);

    /// @notice Sets the contract which implements a specific interface for an address.
    function setInterfaceImplementer(address addr, bytes32 interfaceHash, address implementer) external;

    /// @notice Sets 'newManager' as manager for 'addr'.
    /// The new manager will be able to call 'setInterfaceImplementer' for 'addr'.
    function setManager(address addr, address newManager) external;

    /// @notice Get the manager of an address.
    function getManager(address addr) public view returns(address);

    /// @notice Compute the keccak256 hash of an interface given its name.
    function interfaceHash(string calldata interfaceName) external pure returns(bytes32);

    /// @notice Updates the cache with whether the contract implements an ERC165 interface or not.
    function updateERC165Cache(address contract, bytes4 interfaceId) external;

    /// @notice Checks whether a contract implements an ERC165 interface or not.
    function implementsERC165Interface(address contract, bytes4 interfaceId) public view returns (bool);

    /// @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache.
    function implementsERC165InterfaceNoCache(address contract, bytes4 interfaceId) public view returns (bool);
}

You can then query the registry to check if a given contract address implements an interface with implementsERC165Interface().

Identity

In this section, we’ll look at standards surrounding decentralized identity management.

Attestations are the backbone of trust and reputation in a decentralized identity system. In the real world, we attest our identity with documents like an identity card or passport. These documents, issued by a trusted authority, assert facts about us such as our name, age, address, and more. Identity cards don’t exist on the blockchain. Instead, we need a way to link real-world information to cryptographic addresses.

Some of the projects working on this space include uPort, Origin, Bloom, and Civic.

ERC725 Proxy Account & ERC735 Claim Holder

ERC725 is a proposed standard for blockchain-based identity on the Ethereum blockchain. It describes proxy smart contracts that can be controlled by multiple keys and other smart contracts. ERC735 is an associated standard to add and remove claims to an ERC725 identity smart contract. These identity smart contracts can describe humans, groups, objects, and machines.

The ERC725 standard allows for self-sovereign identity. An open, portable standard for identities enabless decentralized reputation, governance, and more without ceding ownership of identity to centralized organizations. Users will be able to take their identity across different dApps that support the standard.

interface ERC725 {
    event DataChanged(bytes32 indexed key, bytes32 indexed value);
    event OwnerChanged(address indexed ownerAddress);
    event ContractCreated(address indexed contractAddress);

    // address public owner;

    function changeOwner(address owner) external;
    function getData(bytes32 key) external view returns (bytes32 value);
    function setData(bytes32 key, bytes32 value) external;
    function execute(uint256 operationType, address to, uint256 value, bytes calldata data) external;
}

The ERC725 proxy has 2 abilities:

  • It can execute arbitrary contract calls, and
  • It can hold arbitrary data through a generic key/value store. One of these keys should hold the owner of the contract. The owner may be an address or a key manager contract for more complex management logic.

The purpose of an identity proxy is to allow an entity to exist as a first-class citizen in Ethereum, with the ability to execute arbitrary contract calls. It further allows any information to be attached to that proxy.

ERC735 describes standard functions for adding, removing and holding of claims on identity proxies. These claims can be attested from third parties (trusted issuers) or self attested. Trusted third parties can make claims about a particular identity, such as their name and address. dApps and smart contracts can then query an ERC735 registry to check the claims about a claim holder.

contract ERC735 {

    event ClaimRequested(uint256 indexed claimRequestId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
    event ClaimAdded(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
    event ClaimRemoved(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
    event ClaimChanged(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);

    struct Claim {
        uint256 topic;
        uint256 scheme;
        address issuer; // msg.sender
        bytes signature; // this.address + topic + data
        bytes data;
        string uri;
    }

    function getClaim(bytes32 claimId) public constant returns(uint256 topic, uint256 scheme, address issuer, bytes signature, bytes data, string uri);
    function getClaimIdsByTopic(uint256  topic) public constant returns(bytes32[] claimIds);
    function addClaim(uint256 topic, uint256 scheme, address issuer, bytes signature, bytes data, string uri) public returns (uint256 claimRequestId);
    changeClaim(bytes32 claimId, uint256 topic, uint256 scheme, address issuer, bytes signature, bytes data, string uri) returns (bool success);
    function removeClaim(bytes32 claimId) public returns (bool success);
}

ERC1056 Lightweight Identity

ERC1056 describes a standard for creating and updating identities with a limited use of blockchain resources. It specifies a contract called EthereumDIDRegistry that is deployed once and can then be commonly used by everyone. The standard is DID compliant.

An ERC1056 identity can have an unlimited number of delegates (an address that is delegated for a specific time to perform some sort of function on behalf of an identity) and attributes (a piece of data associated with the identity) associated with it. Identity creation is as simple as creating a regular key pair ethereum account, which means that it’s fee (no gas costs) and all ethereum accounts are valid identities.

ERC1812 Ethereum Verifiable Claims

It is both dangerous and in some cases illegal (according to EU GDPR rules for example) to record Identity Claims containing Personal Identifying Information (PII) on an immutable public database such as the Ethereum blockchain.

ERC1812 Reusable Off-Chain Verifiable Claims enables the issuance of off-chain identity claims, based on the typed signing capabilities defined in EIP712. The standard provide an important piece of integrating smart contracts with real world organizational requirements such as meeting regulatory requirements such as KYC, GDPR, Accredited Investor rules etc.

Recurring Payments

ERC1337 Ethereum Subscription Standard

The key monetization channel for businesses on the legacy web are monthly subscriptions. ERC1337 offers a standard interface for recurring subscriptions on the blockchain, similar to existing SaaS business models.

ERC1620 Money Streaming

Money streaming represents the idea of continuous payments over a finite period of time. Block numbers are used as a proxy of time to continuously update balances. With blockchains, payments need not be sent in discrete chunks as there is much less overhead in paying-as-you-go.

ERC1620 describes a standard whereby time is measured using block numbers and streams are mappings in a master contract:

  1. A provider sets up a money streaming contract.
  2. A prospective payer can interact with the contract and start the stream right away by depositing the funds required for the chosen period.
  3. The payee is able to withdraw money from the contract based on its ongoing solvency. That is: payment rate * (current block height - starting block height)
  4. The stream terms (payment rate, length, metadata) can be updated at any time if both parties pledge their signatures.
  5. The stream can be stopped at any point in time by any party without on-chain consensus.
  6. If the stream period ended and it was not previously stopped by any party, the payee is entitled to withdraw all the deposited funds.

Meta Transactions

Communicating with dApps currently requires paying ETH for gas, which limits dapp adoption to ether users. Therefore, contract owners may wish to pay gas on behalf of the user to reduce friction, or let their users pay for gas with fiat money or ERC20 token.

Meta transactions allow a third party to pay transaction costs on behalf of the user. Note that while users do not need to own Ether to broadcast such meta-transactions, someone else needs to spend Ether to allow the content of the transaction to be performed on the network.

EIP865 Gasless Transactions

Introducing wallets and transactions to end users is a challenge, and having to explain that token holders needs ETH to send tokens is adding some friction to the process. The goal of this EIP is to abstract the gas for the end user, by introducing a fee paid in ERC20 tokens. A third party can then bring the transaction on-chain, pay for the gas of that given transaction and get the fee from the user.

EIP865 describes one standard function a token contract can implement to allow a user to delegate transfer of tokens to a third party. The third party pays for the gas, and takes a fee in tokens.

ERC1077 Executable Signed Messages, 1078 Universal Login

Allowing Etherless accounts to sign messages to show intent of execution, but allowing a third party relayer to execute them is an emerging pattern being used in many projects. ERC1077 standardizes a common message format as well as a way in which the user allows the transaction to be paid in ERC20 tokens.

ERC1078 extends this idea further and presents a method to replace the usual signup/login design pattern with a minimal scheme that doesn’t require passwords, backing up private keys, nor typing seed phrases.

ERC1613 Gas Stations Network

The ERC1613 gas stations network is an EIP-1077 compliant effort to removing the user hassle of acquiring ETH. It does this by creating an incentive for nodes to run gas stations, where gasless transactions can be “fueled up”. The network consists of a single public contract trusted by all participating dapp contracts, and a decentralized network of relay nodes (gas stations) incentivized to listen on non-Ether interfaces such as web or whisper, pay for transactions, and get compensated by that contract.

Summary

Standards ERC20 have helped drive innovation in crypto. In this article we’ve examined a wide variety of ERC standards in the Ethereum ecosystem, from token standards to meta transactions.

I hope this exercise gives you a good overview of community efforts in the space. Be sure to keep an eye out for new EIPs.