Token Standards in Ethereum Part-III (ERC1155)
PREREQUISITES
To better understand this post, I recommend you first study the ERC20 and ERC721 standards.
As I said in the earlier post we could only send one token at a time in an ERC721 contract, which results in high gas fees, and it would take much time to send multiple tokens. Apart from this, the gaming community really needed a different token standard that can handle multiple token standards inside one contract. To overcome this problem ERC1155 was introduced in 2018 by Enjin.
ERC1155 can contain different token types with different configurations. Previously in ERC721, we had one contract for one collection and with constant settings for that one collection. Now ERC1155 makes one contract for multiple collections with different configurations possible. This is cool, right? Previously we needed two contracts for fungible and non-fungible tokens and different contracts for different types of non-fungible tokens. Let’s have a look at the working of ERC1155.
What is meant by Multi-Token Standard?
The main idea for this standard is simple and seeks to create a smart contract interface that can represent and control any number of fungible and non-fungible token types. It is proposed in this way, that it can do the same functions as an ERC20 and ERC721 token, and even both at the same time. And best of all, improving the functionality of both standards, making them more efficient, and correcting obvious implementation errors on the ERC20 and ERC721 standards.
Two very important lines written on the Ethereum website-
Smart contracts implementing the ERC1155 standard MUST implement all of the functions in the ERC1155
interface.
Smart contracts implementing the ERC1155 standard MUST implement the ERC165 supportsInterface
function and MUST return the constant value true
if 0xd9b67a26
is passed through the interfaceID
argument.
ERC1155 FUNCTIONS AND FEATURES:
- Batch Transfer: Transfer multiple assets in a single call.
- Batch Balance: Get the balances of multiple assets in a single call.
- Batch Approval: Approve all tokens to an address.
- Hooks: Receive tokens hook.
- NFT Support: If the supply is only 1, treat it as NFT.
- Safe Transfer Rules: Set of rules for secure transfer.
Let’s have a look at the interface.
pragma solidity ^0.5.9;
interface ERC1155 /* is ERC165 */ {
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);
}
I’ll break it down for you.
Events first.
- TransferSingle: The same event as we saw in the previous standards. But why is it transfer single? Well, we know that this is a multi-token standard with a batch transfer functionality, which means we can either send a single token or multiple tokens in one transaction.
So, this event should emit only when a single token is transferred.
event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value);
2. TransferBatch: As suggested by the name this should emit every time there is a batch transfer. Batch transfers are performed by calling the function safeBatchTransferFrom
.
event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values);
3. ApprovalForAll: This event MUST emit whenever the approval for an operator address is enabled or disabled. Notice here, it is written, “approve for all”. Why so? You better guess it.
I will tell you when we talk about the setApprovalForAll function.
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
4. URI
- MUST emit whenever there is an update in the URI for any tokenId.
The URI MUST point to a JSON file that conforms to the "ERC1155 Metadata URI JSON Schema".
event URI(string _value, uint256 indexed _id);
Now have a look at the functions in ERC1155 interface
- safeTransferFrom: Same name same functionality as ERC721 but notice here we have an extra parameter of amount. What is that? It refers to the number of tokens we want to send of a particular token Id, because we have multiple tokens of a single token Id.
I will tell you more about this when talking about the balanceOf function.
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
2. safeBatchTransferFrom
This function works as same as the safeTransferFrom function, the main difference is, it accepts an array of tokenIds and also an array of values, which means multiple tokens can be sent in just one transaction. This is one of the problems ERC1155 solves for ERC721 as ERC721 provides the transfer of a single token in one transaction. One thing to keep in mind while implementing this function is to write the values with respect to the tokenIds. And the length of both arrays should be the same.
function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;
3. balanceOf
This function simply returns the number of tokens of a particular tokenId an address has. Recording the balance of an address is slightly different from the ERC721 contracts. We define a nested mapping like this: mapping(uint => mapping(address => uint))private balance;
In this mapping, the first uint refers to the token id and the address refers to the owner of that token. And finally, it assigns a uint to the mapping which is the balance. That’s why the balanceOf function takes two parameters which are address and tokenId so that it can return balance[_id][_owner]
.
function balanceOf(address _owner, uint256 _id) external view returns (uint256);
4. balanceOfBatch
As the name suggests this function should give out an array of balances of the given account.
Nothing different from the balanceOf
function instead it takes an array and loops through it calling the balanceOf
function. And then it stores the data in an array of balances and returns that array at the end
function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory);
5. setApprovalForAll
After having the knowledge of ERC20 and ERC721 you already know what this function is for. Yes, correct, to approve another wallet/contract to handle your tokens.
There is a slight difference this time. If you approve someone then that wallet/contract is approved for all the tokens.
Remember in ERC721 we had two approval functions, one for a single NFT and the second for all the NFTs. But ERC1155 eliminates the approval for a single token and supports the approval for every token the wallet owns.
To approve someone we always define a nested mapping, just like the balance mapping. mapping(address => mapping(address => bool)) private Approvals
;
And assign values to this mapping through setApprovalForAll
function.
function setApprovalForAll(address _operator, bool _approved) external;
6. isApprovedForAll
As you can see this is a view function. It just checks the approval of an address.
We pass the owner address and operator address and this function returns a bool, true if approved and false if not approved
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
That’s all about the interface for ERC1155.
This might look very similar to the previous ones but it’s not.
There is a lot more in this standard as it handles multiple tokens inside one contract. It has to be robust and secure for wide adaptation.
Similar to ERC721, ERC1155 also has receiver contracts. WHY??
The logic is the same, the receiver contract should have the functionalities to get the tokens out from it otherwise tokens will be locked forever.
Let’s look at the ERC1155TokenReceiver
interface.
onERC1155Received
Handles the receipt of a single ERC1155 token type. An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a safeTransferFrom
after the balance has been updated. This function MUST return bytes4(keccak256(“onERC1155Received(address,address,uint256,uint256,bytes)”))
(i.e. 0xf23a6e61) showing that it is aware of an incoming ERC1155 token and has the functionality to get it out.
This function MUST revert if it rejects the transfer.
Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller.
Look at the parameters this function takes
1. _operator
The address which initiated the transfer (i.e. msg.sender)
2. _from
The address which previously owned the token
3. _id
The ID of the token that is being transferred
4. _value
The number of tokens being transferred
5. _data
Additional data with no specified format
function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _value, bytes calldata _data) external returns(bytes4);
onERC1155BatchReceived
This is mostly the same as the onERC1155Received
. But it is for the batch transfers means multiple types of tokens are being sent with different values each. In this function, instead of a single tokenid,
we would have an array of ids
and the same goes with the values
.
function onERC1155BatchReceived(address _operator, address _from, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external returns(bytes4);
These functions are called from the token contract on the receiver contract. If the receiver is a wallet then no check should be passed on to this address.
Now, what about the implementation? We all definitely agree that openzeppelin has the best implementation on every type of smart contract. But a beginner can’t understand those contracts, that’s why I have tried to implement the contract on my own in the simplest form possible.
HERE is the link to the GitHub repo. You can head over to the Joker.sol contract and you will understand the whole thing.
What else?
ERC1155 is somehow very similar to ERC721, this is the reason why I kept talking about ERC721 throughout the article. Because when you learn multiple topics by finding the similarities and differences between those, then the concept gets super easy to understand.
Now on this note why don’t we talk about metadata Uri for ERC1155?
The interface for ERC1155 metadata .
pragma solidity ^0.5.9;
/**
Note: The ERC165 identifier for this interface is 0x0e89341c.
*/
interface ERC1155Metadata_URI {
/**
@notice A distinct Uniform Resource Identifier (URI) for a given token.
@dev URIs are defined in RFC 3986.
The URI MUST point to a JSON file that conforms to the "ERC1155 Metadata URI JSON Schema".
@return URI string
*/
function uri(uint256 _id) external view returns (string memory);
}
Do you remember that we had a tokenURI function in ERC721 that must be implemented so that it can be called to enquire about the URI of a token?
The same functionality in ERC1155 has been named uri.
Now any client interacting with your ERC1155 contract will expect a uri named function to fetch the URI of a token.
Now talking about the JSON schema for ERC1155. This standard has a bit different schema than ERC721.
{
"title": "Token Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the asset to which this token represents"
},
"decimals": {
"type": "integer",
"description": "The number of decimal places that the token amount should display - e.g. 18, means to divide the token amount by 1000000000000000000 to get its user representation."
},
"description": {
"type": "string",
"description": "Describes the asset to which this token represents"
},
"image": {
"type": "string",
"description": "A URI pointing to a resource with mime type image/* representing the asset to which this token represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
},
"properties": {
"type": "object",
"description": "Arbitrary properties. Values may be strings, numbers, object or arrays."
}
}
}
Does that mean our ERC721 JSON schema would not work with the ERC1155? Well, I tried using the same JSON schema I used for ERC721 inside ERC1155, and it worked. Because both of them are quite similar.
ERC1155 has an additional property of decimals which is a concept of ERC20.
As of now, ERC1155 is not very popular among the NFT projects and those who are using it are implementing it in a simple way.
This standard is mostly used by gaming platforms.
Is that it?
No not really, ERC1155 has many more things to be discussed but I think they are out of scope for this post.
You have learned enough to develop an ERC1155 contract and mint your NFTs.
There are more advanced properties of ERC1155 which I am not including here. You can read about them and play around. It’ll be fun.
ERC1155 has a bunch of rules related to functions which you can read on the official website.
It also describes the measures to be taken in different scenarios. link
The metadata schema is further extended to use a property “localize”. link
There are not so many projects currently which implement ERC1155 using all of the rules and properties. Those which exist are mostly gaming and NFT projects. That means ERC1155 and its capabilities are yet to be explored by us.
Okay so now I believe, you have the knowledge about ERC20, ERC1155, and ERC1155 and also the whole process of NFTs.
That’s it for this series of articles. I’ll be coming back with more in-depth posts about smart contract development and security and also about EVM.
Make sure to connect with me on Twitter.
And share your views on my articles, and any kind of review so that I can be aware of my strength and weakness and provide more value to the community.
New to trading? Try crypto trading bots or copy trading on best crypto exchanges