ERC1155
The ImmutableERC1155 contracts allows clients to mint semi-fungible tokens according to the EIP-1155 Multi Token Standard. ERC1155 tokens can represent both fungible and non-fungible assets, making them ideal for various Web3 gaming in-game items such as consumables and in-game currencies. They offer advantages over other standards like ERC721 (non-fungible tokens) and ERC20 (fungible tokens) by allowing multiple tokens to be managed within a single contract, reducing gas costs and simplifying asset management for game developers.
Key functionality | Recommended usage |
---|---|
|
|
Installation, usage and deployment
Deploy with the Immutable Hub
ERC1155 preset contracts can be deployed seamlessly via the Immutable Hub. You can do this by navigating to your project and environment and clicking on the "Contracts" submenu on the left hand side. You can then hit the "Deploy" button to deploy one of our supported preset contracts.
Alternatively, they can be deployed manually by following the instructions below.
Installation
The preset contract is contained within the contracts repository which can be easily added to your project via:
npm install @imtbl/contracts
We recommend using existing frameworks such as Hardhat or Foundry in order to set up your own smart contract development environment from scratch.
Usage
The contracts repository can be used to:
- develop and deploy your own smart contract using one of Immutable's presets
- interact with your already deployed Immutable preset contract using the Typescript client
Smart contract development
pragma solidity 0.8.19;
import '@imtbl/contracts/contracts/token/erc1155/preset/ImmutableERC1155.sol';
contract MyERC1155 is ImmutableERC1155 {
constructor(
address owner,
string memory name,
string memory baseURI,
string memory contractURI,
address operatorAllowlist,
address royaltyReceiver,
uint96 feeNumerator
)
ImmutableERC1155(
owner,
name,
baseURI,
contractURI,
operatorAllowlist,
royaltyReceiver,
feeNumerator
)
{}
}
Constructor arguments
Argument | Purpose |
---|---|
owner | The address to grant the DEFAULT_ADMIN_ROLE to, for administrative permissions |
name | The name of the collection |
baseURI | The base URI for the collection, used for retrieval of off-chain token metadata |
contractURI | The contract URI for the collection, used for retrieval of off-chain collection metadata |
operatorAllowlist | The address of the operator allowlist |
royaltyReceiver | The address of the royalty receiver, a single recipient for the entire collection |
feeNumerator | The royalty fee numerator, specified in basis points. For example, setting the numerator to 200 will result in a 2% royalty fee |
Deployment
To deploy smart contracts on Immutable zkEVM, see the deploying smart contracts quickstart guide.
Configure Hardhat
Update your project's hardhat.config.js/ts
file with the following code.
This file is located in your smart contract repository.
If you want to be able to manage multiple solidity versions, refer to the Hardhat documentation on Multiple Solidity Versions.
solidity: {
version: "0.8.17",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
Functionality and design
Whilst the contract embeds the recommended functionality, it can also be extended or refactored to fit the developer's needs. With a caveat of extending functionality may introduce vulnerabilities inside your smart contracts, care should be taken.
Functionality
- Minting
- Burning
- Metadata
- Access control
- Royalty specification and retrieval
- Operator allowlist: Royalty and Protocol fee enforcement
- Permits
Minting
Minting an ERC1155 NFT creates a unique asset on the blockchain specific to the contract. With ERC1155, each unique asset can represent a fungible or non-fungible token type, with each ID having its own supply and metadata. The supply of a particular ID can be increased by minting more of the same token ID, or reduced by burning part of the supply.
Read more about semi-fungible tokens.
The preset uses one of two methods to mint tokens:
safeMint
safeMintBatch
Both methods are permissioned, allowing only users with the minter role to mint an amount of tokens to a specified address. For further information about permissions, refer to the access control section below.
Safe methods for minting are provided to ensure the safety of the operation, by ensuring that the receiver of the transaction is either a wallet or a contract that can receive ERC1155 tokens. If the destination address of a safe mint call is a contract, the contract must implement the IERC1155Receiver.onERC1155Received
method for the transaction to complete successfully.
Burning
Burning reduces the total supply of an ERC1155 NFT by destroying them.
The preset uses one of two methods to burn token amounts:
burn
burnBatch
The user performing the burn must have approval for the particual tokens they are burning.
Metadata
The ImmutableERC1155
preset contract supports metadata at the token and collection level in the same way as the ERC721 preset contracts.
Metadata is an integral part of an ERC1155 collection, defining its unique attributes and utility; the collection owner defines this data. Metadata in a collection is referenced by its universal resource identifier (URI). The metadata source may be stored either on-chain or off-chain, supporting different schemas.
Metadata can be a mutable object and is often updated by the collection owners. This may relate to the specific utility or mechanics of the collection and means that services integrating with the collection need to have the latest metadata in their system.
The baseURI
and contractURI
are set in the preset's constructor, and the preset also exposes functions to update these.
Types
There are three canonical metadata URIs inside an ERC1155 collection:
Base URI: single metadata URI that is the common denominator across all token IDs
URI : metadata URI specific to a single token ID
Contract URI: single metadata URI that is collection-wide. E.g. this is used by marketplaces to provide store-front information
Storage
Storing metadata off-chain means that the data is stored outside the contract's storage in an off-chain location. A pointer to the off-chain metadata source is stored within the contract's storage.
Storing metadata on-chain means the data is stored inside the contract’s storage. The format of on-chain metadata is often, but not always, in JSON format, which should be handled by integrating systems accordingly.
On-chain metadata is still a pattern that isn’t as widely adopted as off-chain metadata in the ecosystem and thus has less support and tooling (as well as the increased gas cost), lacking the richness that off-chain storage can provide. This makes off-chain metadata storage the recommended approach.
Schemas
Metadata schemas provide a standard that allows applications that integrate with your ERC1155 contract to know what the expected returned metadata is and how to interpret it. OpenSea’s metadata schema is the most adopted schema by the community for defining and understanding metadata within the ecosystem, which is supported for both on and off-chain metadata. The schema can be found here. For this reason, it is the recommended schema when defining metadata (this applies for the schema for both the contractURI
and baseURI
).
The format for used in OpenSea's schema is JSON. An example is shown below:
{
"description": ""
"external_url": "",
"image": "",
"name": "",
"attributes": []
}
Traits
The schema used by OpenSea supports attributes that allow creators to provide additional information about their NFTs. Within these attributes, are traits which allow you to describe the attributes of an NFT as well as format their display. The three different trait types are numeric, date and string. For example, one may use these traits to describe a hero NFT as follows:
{
"attributes": [
{
"trait_type": "Hero",
"value": "Fighter"
},
{
"display_type": "number",
"trait_type": "Strength",
"value": 2
}
]
}
For further information on using attributes and their traits in your metadata, refer to here.
ID Substitution
The ImmutableERC1155
preset supports ID substitution as described in the EIP-1155.
To opt-in to using ID substitution, provide your URL containing the {id}
placeholder to the baseURI
constructor argument when deploying your ImmutableERC155
contract.
For ERC1155 collections, any metadata response values containing the {id}
placeholder will also be substituted with the actual token ID (in zero-padded hexadecimal format).
If the presence of the {id}
placehoder is detected, the system will use the zero-padded hexadecimal representation of the ID when performing the substitution in order to obtain the metadata URL to be crawled. If no {id}
placeholder is present, the system will revert to the standard baseURI/token_id
(numeric) concatenation approach.
Access control
The preset contract inherits OpenZeppelin's AccessControl module, which allows you to define role-based access modifiers for particular functions, such as minting.
Access control defines the ontology of roles inside a contact. A role is a structure with a list of members and a defined admin role, with each member within this structure having the admin role. For further information on access control, refer to OpenZeppelin's documentation here.
The modifier onlyRole()
is used to restrict functionality to admins of that role. For example, only addresses with the MINTER
role may execute the function mint
. The DEFAULT_ADMIN_ROLE
is the other role inside the preset which has restricted functionality. These functions include:
setBaseURI
allows the admin to update the base URI.setContractURI
allows the admin to update the contract URI.grantMinterRole
allows the admin to grant theMINTER
role to the user.transferOwnership
allows the admin to set a new owner inside the contract.
The ontology of an owner is within the preset contract, for display purposes (e.g. a marketplace storefront will often display metadata about the collection, for example, the collection owner signalled by the contract owner). The owner is set on contract deployment and is the same address set for the DEFAULT_ADMIN_ROLE
. Ownership may be transferred to other addresses, as long as they are a member of the DEFAULT_ADMIN_ROLE
. Note that this implementation is not OpenZeppelin's Ownable standard. For more information on Ownable, refer to here.
Royalty specification and retrieval
EIP-2981 defines the specification and retrieval of royalties. The present contract defines the royalty specification as a fraction of the sale price. That is, given a tokenID
and a salePrice
, the royalty amount returned is a fraction of the sale price, specified in basis points. This is what will be returned on royalty retrieval for a tokenId. The EIP can be found here.
The preset's implementation of EIP-2981 does not include any bespoke royalty calculation logic, and returns a fixed percentage of the sale price. The royalty percentage is defined on contract creation, and the preset does not expose a function to modify this afterwards.
Operator allowlist: Royalty and Protocol fee enforcement
All collections must use Immutable's operator allowlist to protect a collection's royalties.
The Operator Allowlist Address value in the table below should be used as the operatorAllowlist
parameter.
Chain Name | Chain ID | Operator Allowlist Address |
---|---|---|
imtbl-zkevm-testnet | eip155:13473 | https://api.sandbox.immutable.com/v1/chains returns operator_allowlist_address |
imtbl-zkevm-mainnet | eip155:13371 | https://api.immutable.com/v1/chains returns operator_allowlist_address |
Immutable's preset contracts incorporate an operator allowlist to safeguard game studios' royalties and Immutable's protocol fee during secondary market trading.
This allowlist protects against vampire attacks, preventing unauthorized trading on orderbooks or settlement smart contracts that might bypass content creators' revenues (i.e., royalties) and Immutable's 2% fee.
The enforcement of the operator allowlist is achieved in the following function call within the OperatorAllowlistEnforced.sol
file, found in Immutable's contracts repository:
function _setOperatorAllowlistRegistry(address _operatorAllowlist) internal {
if (!IERC165(_operatorAllowlist).supportsInterface(type(IOperatorAllowlist).interfaceId)) {
revert AllowlistDoesNotImplementIOperatorAllowlist();
}
All game studios are required to inherit OperatorAllowlistEnforced.sol to ensure that their royalties and Immutable's fees are properly respected during secondary market trading. For more details on royalty enforcements, refer to the article.
Permits
This present contract supports permits. For more information on permits please see our Permits product guide.
Interface
Function | Description |
---|---|
safeMint(to, id, amount) | Allows minter role to safely mint an amount of token with ID id to to |
safeMintBatch(to, ids, amounts) | Allows minter role to safely mint amounts of token IDs ids to to |
burn(account, id, amount) | Burns an amount of the token with ID tokenId for user account (requires ownership or approval) |
burnBatch(account, ids, amounts) | Burns amounts of the tokens with IDs specified in ids for user account (requires ownership or approval) |
safeTransferFrom(from, to, id, amount) | Safely transfers an amount of the token with ID tokenId from user from to to (requires ownership or approval) |
safeBatchTransferFrom(from, to, ids, amounts) | Safely transfers amounts of the tokens with IDs specified in ids from user from to to (requires ownership or approval) |
grantMinterRole(user) | Grant a user the minter role, restricted to admins |
totalSupply(id) | Returns the total supply value of token with ID id |
baseURI() | Returns the base URI |
contractURI() | Returns the contract URI |
owner() | Returns the current owner of the contract |
setBaseURI(baseURI_) | Set the base URI, restriced to admins |
setContractURI(_contractURI) | Set the contract URI, restricted to admins |
transferOwnership(address newOwner) | Transfer contract ownership, updating contract owner, restricted to current owner |
supportsInterface(interfaceId) | Returns whether contract supports supplied interface ID |
Inheritance
Contract | Functionality |
---|---|
ImmutableERC1155Base (code, EIP) | Base abstract contract that inherits OperatorAllowlistEnforced, ERC1155Permit, ERC2981 and adds functionality for metadata, role setting and retrieval and total supply |
ERC1155Permit (code, EIP) | Adds the permit method based on OpenZeppelins IERC20Permit contract |
ERC1155Burnable (code, EIP) | OpenZeppelin extension of ERC1155 that allows for destruction of tokens that a user owns or has approval for |
ERC1155 (code, EIP) | OpenZeppelin implementation of the basic standard multi-token |
IERC2981 (code, EIP) | Royalty specification and retreival |
AccessControl (code, EIP) | Role setting (granting and revoking), role retrieval, role creation |