In this article, we will explore what ERC-1155 is, how it differs from ERC-20 and ERC-721, and why it is a game-changer for developers looking to streamline token management. Additionally, we will walk through a practical example of an ERC-1155 smart contract, demonstrating how to mint, transfer, and manage both fungible and non-fungible tokens. To conclude, we will test the smart contract using Remix, a popular development tool for Ethereum smart contracts, to see how the theory translates into practice.

By the end of this article, you will have a comprehensive understanding of ERC-1155 and be ready to implement it in your own projects.

Table Of Contents

· Table Of Contents
· The ERC-1155 Standard
Combining ERC-20 and ERC-721 functionalities
Comparison between ERC-20, ERC-721 and ERC-1155
· ERC-1155 Interfaces
· ERC-1155 Smart Contracts
· Creating our ERC1155 Smart Contract
· Remix Testing
Balances
Transferring Tokens
Minting Tokens
References

The ERC-1155 Standard

The ERC 1155 is a standard interface for contracts that manage multiple token types. A single deployed contract may include any combination of fungible tokens, non-fungible tokens or other configurations (e.g. semi-fungible tokens).

Combining ERC-20 and ERC-721 functionalities

ERC-1155 is a token standard on the Ethereum blockchain that enables the creation and transfer of both fungible and non-fungible tokens within a single transaction.

ERC-1155 combines the functionalities of earlier standards, such as ERC-20 (for fungible tokens) and ERC-721 (for non-fungible tokens), and even supports both simultaneously. By enhancing the features of these standards, it offers a more efficient and versatile solution for token management.

ERC-1155 offers several features, including support for an unlimited number of tokens and semi-fungible tokens.

ERC-1155 works by allowing multiple items to be stored within a single smart contract. This means that any number of items can be sent in a single transaction to one or more recipients, aiming to reduce the costs and complexity of transactions. For example, you could send a sword, a shield, and some gold coins to a friend effortlessly, all in one transaction.

Although ERC-1155 is highly beneficial for gaming applications, its versatility goes well beyond this domain. It seamlessly supports a wide range of token types, including currencies, collectibles, and specialized tickets.

Before ERC-1155, each token type required a separate contract, leading to redundant duplication of similar functionalities. ERC-1155 eliminates this inefficiency by unifying multiple token types within a single contract, optimizing storage and simplifying token management.

ERC-1155 introduces a safety mechanism that enables the recovery of tokens mistakenly sent to the wrong address. This marks a substantial advancement over earlier token standards, offering users greater security and confidence.

Comparison between ERC-20, ERC-721 and ERC-1155

ERC-1155 Interfaces

The ERC-1155 standard consists of three interfaces, each serving a specific role: IERC1155, IERC1155MetadataURI, and IERC1155Receiver.

IERC1155 is the core interface of the ERC-1155 standard, defining the fundamental functionality.IERC1155MetadataURI specifies the uri method, which retrieves the URI associated with a specific token ID.IERC1155Receiver is the interface that smart contracts must implement to handle incoming ERC-1155 token transfers.

The IERC1155 interface defines the following methods:

balanceOf(account, id)
balanceOfBatch(accounts, ids)
setApprovalForAll(operator, approved)
isApprovedForAll(account, operator)
safeTransferFrom(from, to, id, value, data)
safeBatchTransferFrom(from, to, ids, values, data)
supportsInterface(interfaceId)

In particular:

balanceOf: Retrieves the balance of an account’s tokens.balanceOfBatch: Retrieves the balances of multiple account/token pairs.setApprovalForAll: Enables or disables approval for a third party (referred to as an “operator”) to manage all of the caller’s tokens.isApprovedForAll: Queries the approval status of an operator for a given owner.safeTransferFrom: Transfers a specified _value amount of a token with ID _id from the _from address to the _to address, with a safety call.The caller must be approved to manage the tokens being transferred from the _from account.safeBatchTransferFrom: Transfers specified _values of multiple tokens with IDs _ids from the _from address to the _to address, with a safety call. The caller must be approved to manage the tokens being transferred from the _from account.

For further exploration, check out the official ERC-1155 page.

ERC-1155 Smart Contracts

The ERC1155 contract provides an implementation of the methods defined in the IERC1155 interface. OpenZeppelin offers a robust implementation of this contract, which is available in this repository.

The ERC1155 contract provides an implementation of the methods defined in the IERC1155 interface. OpenZeppelin offers a robust implementation of this contract, which is available in their repository.

In addition, several extensions enhance the basic ERC1155 functionality, including:

ERC1155Pausable: Enables pausing of token transfers, minting, and burning. This is useful in scenarios like delaying trades until an evaluation period ends or activating an emergency freeze in case of a major issue.ERC1155Burnable: Allows token holders to destroy their own tokens or tokens they are approved to manage.ERC1155Supply: Tracks the total supply of tokens per ID. This is particularly helpful when distinguishing between fungible and non-fungible tokens.ERC1155URIStorage: Provides URI management for tokens using on-chain storage, inspired by the ERC721URIStorage extension.

Additionally, there is a utility contract called ERC1155Holder, which is a simple implementation of IERC1155Receiver that allows a contract to hold ERC-1155 tokens.

Creating our ERC1155 Smart Contract

Since the ERC1155 standard can handle both fungible and non-fungible tokens within the same smart contract, let’s imagine creating a contract to manage the vehicles in a game, such as these:

GTA Vehicles

You can check the final tokens collections on OpenSea.

This is the smart contract :

// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.28;

import {ERC1155} from “@openzeppelin/contracts@5.1.0/token/ERC1155/ERC1155.sol”;
import {ERC1155Supply} from “@openzeppelin/contracts@5.1.0/token/ERC1155/extensions/ERC1155Supply.sol”;
import {ERC1155URIStorage} from “@openzeppelin/contracts@5.1.0/token/ERC1155/extensions/ERC1155URIStorage.sol”;
import {Ownable} from “@openzeppelin/contracts@5.1.0/access/Ownable.sol”;
import {Strings} from “@openzeppelin/contracts@5.1.0/utils/Strings.sol”;

contract GTAVehicles is ERC1155, Ownable, ERC1155Supply, ERC1155URIStorage {
string public constant name = “GTA Vehicles”;
uint256 public tokenCounter = 4;

constructor(address initialOwner) ERC1155(“”) Ownable(initialOwner) {
// SENTINEL = 0
_mint(msg.sender, 0, 10**4, “”);
ERC1155URIStorage._setURI(
0,
“ipfs://bafkreigjgrjetsnmsb4ouzu7o5i4jfuktrxu5egbxzhpntetwq2teq5pp4”
);
// KURUMA = 1
_mint(msg.sender, 1, 10**5, “”);
ERC1155URIStorage._setURI(
1,
“ipfs://bafkreiga3py5q645lxjggsvmaoi6w3252j7ekrwezcouwbvzk2wnmf4gaq”
);
// TRAIN = 2
_mint(msg.sender, 2, 1, “”);
ERC1155URIStorage._setURI(
2,
“ipfs://bafkreigobjksa7dsdn7kebcuc2eyhje564qhwea35tjbjoo2nz5vsj73le”
);
// BANSHEE = 3
_mint(msg.sender, 3, 10**2, “”);
ERC1155URIStorage._setURI(
3,
“ipfs://bafkreif3q4uq4jvv6xk54xrkxtx6zdh7entphgkx2mxpkxughtsw7ssu3m”
);
// INFERNUS = 4
_mint(msg.sender, 4, 10, “”);
ERC1155URIStorage._setURI(
4,
“ipfs://bafkreihlo2qz3uhk5udvyauc3h4yv7oy7yorkeytuykwbr3amamkx74rjq”
);
}

function uri(uint256 tokenId)
public
view
override(ERC1155, ERC1155URIStorage)
returns (string memory)
{
require(tokenId <= tokenCounter, “Token ID does not exist”);
return ERC1155URIStorage.uri(tokenId);
}

function mint(
address account,
uint256 amount,
bytes memory data,
string memory tokenUri
) public onlyOwner {
_mint(account, ++tokenCounter, amount, data);
ERC1155URIStorage._setURI(tokenCounter, tokenUri);
}

function mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) public onlyOwner {
_mintBatch(to, ids, amounts, data);
}

// The following functions are overrides required by Solidity.

function _update(
address from,
address to,
uint256[] memory ids,
uint256[] memory values
) internal override(ERC1155, ERC1155Supply) {
super._update(from, to, ids, values);
}
}

The smart contract GTAVehicles inherits from the ERC1155, Ownable, ERC1155Supply, and ERC1155URIStorage contracts, thereby inheriting their methods and events:

contract GTAVehicles is ERC1155, Ownable, ERC1155Supply, ERC1155URIStorage

In the constructor, we mint four tokens by calling the _mint function:

function _mint (address internal to, uint256 internal id, uint256 internal value, bytes internal data)

To mint a fungible token, we pass a value greater than 1 to the _mint function. For example, in the following case, we mint a token with ID 0 and pass a value of 10**4, which means we are creating 10,000 tokens.

_mint(msg.sender, 0, 10**4, “”);

On the other hand, to create a non-fungible token, we simply call the _mint function with a value of 1, as demonstrated when minting the token with ID 2:

_mint(msg.sender, 2, 1, “”);

Each token is associated with a URI, which links to a file containing the metadata for that specific token. To manage the URIs for each token, we use the ERC1155URIStorage contract. This contract defines the _tokenURIs mapping, which serves this purpose:

mapping(uint256 tokenId => string) private _tokenURIs;

To keep track of the URIs for each token, we call the _setURI function from the ERC1155URIStorage contract after minting a new token. Specifically, in the constructor, this function is called for each newly minted token.

ERC1155URIStorage._setURI(
0,
“ipfs://bafkreigjgrjetsnmsb4ouzu7o5i4jfuktrxu5egbxzhpntetwq2teq5pp4”
);

In the previous code block, we set the URI for the token with ID 0 to ipfs://bafkreigjgrjetsnmsb4ouzu7o5i4jfuktrxu5egbxzhpntetwq2teq5pp4 because we uploaded the JSON metadata for our tokens to IPFS. For example, the JSON metadata for token 0 is:

{
“name”: “Sentinel”,
“description”: “A classic luxury sedan from GTA III, favored by mafia bosses.”,
“image”: “ipfs://bafkreid5ol54ymiw2ovkufwzk3rsr2kbkfwhwbpzh6mahs3yjv45k3bax4”,
“attributes”: [
{
“trait_type”: “Type”,
“value”: “Luxury Sedan”
},
{
“trait_type”: “Top Speed”,
“value”: “180 km/h”
},
{
“trait_type”: “Acceleration”,
“value”: “7.5/10”
},
{
“trait_type”: “Drivetrain”,
“value”: “Rear-Wheel Drive”
}
]
}

This is a metadata format for ERC1155 and ERC721 tokens as defined by OpenSea. In the image field, you can see another IPFS link, as the image associated with this token has also been uploaded to IPFS.

To upload files on IPFS you can use different services but in this case I have used Pinata.

The uri function is used by OpenSea to retrieve the metadata associated with each token we mint. We have defined it as follows:

function uri(uint256 tokenId)
public
view
override(ERC1155, ERC1155URIStorage)
returns (string memory)
{
require(tokenId <= tokenCounter, “Token ID does not exist”);
return ERC1155URIStorage.uri(tokenId);
}

Since our smart contract allows minting new tokens using the mint function, we keep track of the number of tokens minted using the tokenCounter variable. In the uri function, we essentially return the result of calling the uri function from the ERC1155URIStorage contract:

function uri(uint256 tokenId) public view virtual override returns (string memory) {
string memory tokenURI = _tokenURIs[tokenId];

// If token URI is set, concatenate base URI and tokenURI (via string.concat).
return bytes(tokenURI).length > 0 ? string.concat(_baseURI, tokenURI) : super.uri(tokenId);
}

In our case, the uri function will always return the value stored in the _tokenURIs mapping for the given key, which is the tokenId. This is because the _baseURI variable is an empty string, and bytes(tokenURI).length will always evaluate to true. Furthermore, the check require(tokenId <= tokenCounter, “Token ID does not exist”); ensures that the uri function cannot be called for a tokenId that does not exist.

The mint function simply calls the _mint function of the ERC1155 contract and, after that, sets the URI for the newly minted token.

function mint(
address account,
uint256 amount,
bytes memory data,
string memory tokenUri
) public onlyOwner {
_mint(account, ++tokenCounter, amount, data);
ERC1155URIStorage._setURI(tokenCounter, tokenUri);
}

The mintBatch function simply calls the _mintBatch function of the ERC1155 contract.

Remix Testing

To test the smart contract, I deployed it using Remix on the Base Sepolia network with the Metamask wallet. The contract owner is my address, 0x1F0c72E13718D9136FfE51b89289b239A1BcfE28, which I passed as an argument to the constructor:

The smart contract has been deployed at the address: 0x981eab3d7C6836b4B7bc0Cc4aCd97559247391A3.

Balances

To check the balance of a token for a specific account, there are two methods: balanceOf, which allows you to check the balance for a single token ID, and balanceOfBatch, which allows you to check the balance of multiple token IDs for multiple accounts.

To test the balanceOf function and check the balance of the account 0x1F0c72E13718D9136FfE51b89289b239A1BcfE28 for the token with ID 4:

The result is 10 because we passed msg.sender to the _mint function, and since the account that deployed the smart contract is 0x1F0c72E13718D9136FfE51b89289b239A1BcfE28, it is correct that it has 10 tokens of ID 4. This is how the _mint function was called in the constructor to mint the token with ID 4:

_mint(msg.sender, 4, 10, “”);

The ERC1155 standard defines the balanceOfBatch function, which allows us to check the balances of multiple tokens at once:

The function receives two arrays as input: accounts and ids. This allows us to check the balance of specific token IDs for multiple accounts. In this case, we are checking the balance of token 0 for the account 0x1F0c72E13718D9136FfE51b89289b239A1BcfE28 and the balance of token 1 for the account 0x350a97Aa777CcfE518197C34342C5bA262825B35.

Transferring Tokens

Similar to the balance methods, the ERC1155 standard provides two methods for transferring tokens: safeTransferFrom for transferring a single token ID and safeBatchTransferFrom for transferring multiple token IDs.

Let’s start with the safeTransferFrom function:

We are transferring 1,000 tokens of ID 0 from 0x1F0c72E13718D9136FfE51b89289b239A1BcfE28 to 0x350a97Aa777CcfE518197C34342C5bA262825B35. The transaction hash is 0xccdc3e8fa791ffaa9de15e34c7baee89007d49bf9fa30540c8c850008d7372a7, which can be checked on Base Sepolia Scan.

We can also call the balanceOf function to double check:

Let’s also test the safeBatchTransferFrom function:

In this case, we are transferring 100 tokens of ID 1 and 10 tokens of ID 3 from 0x1F0c72E13718D9136FfE51b89289b239A1BcfE28 to 0x350a97Aa777CcfE518197C34342C5bA262825B35.

The transaction was validated with the hash: 0x567957b344d7889fae5483648345c676b7f849018c8c78ae0712d4ce068ee386. It can also be checked on Base Sepolia Scan. In this case let’s call the balanceOfBatch to double check:

Minting Tokens

Similarly, for minting new tokens, there are two functions: mint for minting a single token and mintBatch for minting multiple tokens at once.

To test the mint function, I used the following parameters:

The transaction 0xe76b99510f92ab3651165d7e0b03f35ed5ae61d363e97427f4b2888039bc151c was created, and it is possible to verify on Base Sepolia Scan that it successfully minted a new token of ID 5.

Let’s test the mintBatch function:

In this case, we are minting 10 more tokens of ID 10 and 100 more tokens of ID 1 to the address 0x20c6F9006d563240031A1388f4f25726029a6368. The transaction hash is 0xe8126569d46d36dc5cc3039b25c802221df15d3394ab91f01044bceae81d21af and can be verified on Base Sepolia Scan.

References

https://ethereum.org/en/developers/docs/standards/tokens/erc-1155/https://www.coinbase.com/it/learn/crypto-glossary/what-is-erc-1155https://academy.binance.com/en/articles/what-is-erc-1155-and-how-does-it-workhttps://eips.ethereum.org/EIPS/eip-1155#metadatahttps://docs.openzeppelin.com/contracts/5.x/api/token/erc1155https://docs.opensea.io/docs/metadata-standards

Understanding ERC-1155: The Ultimate Token Standard for Fungible and Non-Fungible Assets was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.

By

Leave a Reply

Your email address will not be published. Required fields are marked *