There are a handful of different token standards on Ethereum. For example, you have ERC20 for fungible tokens like ChainLink or Uniswap and ERC721 for non-fungible tokens like digital art, in-game items and event tickets.

What if you have a Dapp that utilizes both? For example you have a game that gives players a character represented by a non-fungible token. These characters can also collect in game items like gold, armor and potions. These could be represented by fungible tokens.

You could create separate token contracts for each type of in game item but that would become a nightmare pretty quickly.

ERC 1155 Solves this by allowing developers to manage fungible, semi-fungible and non-fungible tokens all in one contract. This is far more efficient and saves in both time and cost in resources like gas when interacting with the contract.

Let's have a a look at what an ERC1155 contract looks like.

pragma solidity ^0.5.9;

interface ERC1155 {

    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);

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

    event URI(string _value, uint256 indexed _id);

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

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

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

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

    function setApprovalForAll(address _operator, bool _approved) external;

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

As you can see the interface is pretty similar to ERC20 and ERC721 with some slight differences. The main difference is that the balanceOf method requires an id which is used to reference the specific token in the contract. There are also approval functions for allowing other addresses to send tokens on the user's behalf. Let's look at the interface in more detail.

pragma solidity ^0.5.9;

interface ERC1155 {

    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);

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

        event URI(string _value, uint256 indexed _id);
}

There are 4 required events for ERC1155.

  • TransferSingle occurs when a single token is transferred or when a token is initially minted.
  • TransferBatch occurs when several different tokens are transferred or initially minted.
  • ApprovalForAll occurs when an owner has given approval for another operator to send their tokens.
  • URI occurs when a URI for a specific token id is added.
pragma solidity ^0.5.9;

interface ERC1155 {

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

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

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

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

    function setApprovalForAll(address _operator, bool _approved) external;

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

There are 6 required methods for ERC115.

  • safeTransferFrom is for transferring a specific token in a safe way to a valid ERC1155 token receiver which I will explain in a little later.
  • safeBatchTransferFrom is the same but for a list of tokens. This is how ERC1155 saves gas.
  • balanceOf obviously lists an address' balance for a specific token.
  • balanceOfBatch gives a list of balances for a list of tokens.
  • setApprovalForAll gives an address permission to spend another address' tokens. This is method an online marketplace like Open Sea would use to list your tokens for sale.
  • isApprovedForAll will show whether or not an address is approved to spend another address' tokens.

Earlier I mentioned that you could transfer tokens in a safe way to a valid ERC1155 receiver. This is done by requiring receiving smart contracts to implement the ERC1155TokenReceiver interface. Here is what it looks like.

pragma solidity ^0.5.9;

interface ERC1155TokenReceiver {

    function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _value, bytes calldata _data) external returns(bytes4);

    function onERC1155BatchReceived(address _operator, address _from, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external returns(bytes4);       
}

A contract that implements these methods should be able to receive tokens safely without them getting stuck in the contract and lost forever. An ERC1155 contract should prevent transfers to contracts that do not implement these methods.

That's it. In a future video, I'll show you how to implement your own contract using OpenZeppelin's out the box contract library.