Skip to main content

Batch Minting

On this page, we explore the ways minting in batches reduces gas fees and network congestion. You will learn what game design scenarios are best suited to minting in batches and how to perform a batch mints using the tools provided by Immutable's preset contracts.


What is batch minting?

Batch minting of NFTs is the process of creating multiple NFTs at once on a blockchain (i.e. minting). In traditional NFT minting, each NFT is created individually, requiring a separate transaction. Batch minting streamlines this process by allowing multiple NFTs to be minted in a single transaction, reducing the number of transactions needed and often resulting in lower gas fees.

Batch minting is particularly useful when a game studio or NFT project needs to mint a large number of tokens simultaneously. This could be for various reasons, such as launching a new collection, distributing rewards, or facilitating in-game assets for multiple users. By batching these minting operations, the cost and time associated with gas fees and transaction confirmation are significantly reduced, making the overall process more efficient.

The concept of batch minting aligns with the broader goal of optimizing blockchain operations, reducing costs, and enhancing scalability, especially in scenarios where the creation of a large number of NFTs is required.

tip

Batch minting functions require all assets within a batch to belong to the same collection. Games that have fewer collections that contain diverse assets will be able to capitalise on batch minting functions as there will be more opportunities to mint batches of assets. For example: If a sword, shield and helmet all belong to the same collection - a player being rewarded these items at the completion of a level can utilise a batch mint. If each asset is in a different collection this would not be possible.

How does batch minting save gas?

Immutable’s batch minting strategies save gas in the following ways:

Reducing Transactional Overheads

Every blockchain transaction carries computational and storage costs. Batch minting for NFTs reduces these costs by consolidating multiple minting actions in a single transaction. This efficiency significantly lowers gas fees compared to minting NFTs individually, where fees accumulate per transaction.

Batch minting leverages economies of scale, allowing the creation of multiple NFTs with a lower collective gas fee.

The mintBatch() function on Immutable's recommended ERC721 Preset and ERC721 By-ID Only Preset is designed to reduce transactional overheads.

Minimizing Blockchain Data Storage

Each NFT minting operation adds data to the blockchain, incurring gas costs for the initiator. If a function is designed to mint assets in a manner that necessitates less on-chain data storage, the associated gas fees required to perform the action will be reduced.

Conventional single NFT mint() entails specific blockchain data for NFT identity and ownership. In contrast, a batch minting function which optimizes data arrangement reduces data commitment and ensuing gas fees.

Immutable's recommended ERC721 Preset features the mintBatchByQuantity() method that curbs Blockchain data by attributing one account_address to multiple NFTs minted to a wallet. The initial NFT of a batch has a defined account_address, but subsequent ones show null for account_address, economizing on-chain data. The next NFT with a populated account_address signifies the next batch's single owner. This is represented on the diagram below:

BulkMintDataSaving

When encountering an NFT on the chain without an account_address, examining sequentially lower Token ID NFTs reveals the first instance where an NFT with a populated account_address indicates the account_address for the NFT with the null value.

It is important to note that if the above methodology is implemented; the minter cannot specify the Token ID for each NFT, as they must be sequentially organised by account_address.

note

Immutable's recommended ERC721 Preset has a more efficient way of locating the account_address using bitmaps and a de Bruijn sequence. Sequentially looking at each previous NFT conceptually helps illustrate the data structure; however is not optimised.

When to use batch minting functions

Batch minting is the recommended approach when a player or game studio plans to create multiple assets. In all cases, batch minting multiple NFTs incurs lower gas costs than individually minting each NFT in separate transactions.

note

An exception is if the minter times single mints during lower network congestion, but this could lead to a prolonged unpredictable minting process. Batch minting ensures nearly simultaneous creation of assets.

The following gaming scenarios are suited to batch minting:

  • Primary Sales: Primary sales refer to the initial offerings of digital assets, often in the form of NFTs, directly from the creator or issuer to buyers. When a player buys more than 1 item from a collection, batch minting can lower the cost of the transaction.
  • New Player Assets: When a new player signs up to a game they are often allocated a starter kit of assets that can engage with the game. Batch minting these assets to the player wallet can lower client acquisition costs, whilst still giving new players the opportunity to own Web3 assets.
  • Event Giveaways: Event giveaways involve players completing studio-dictated tasks for rewards. These range from in-game tournaments to signing up for services by specific dates. Batch minting can be used to efficiently allocate assets to a large user group at predetermined point in time (i.e. when a tournament ends at Sunday midnight), curbing costs for game studios.
  • Player Engagement: Games often motivate player engagement through asset rewards for achievements like winning matches or completing levels. When these rewards involve minting multiple assets from the same collection, batch minting becomes a cost-effective approach. This empowers studios to provide more assets, fostering increased player engagement.
  • Loot boxes: Loot boxes are purchasable or earnable virtual items in video games. When opened, they yield randomized in-game items like characters, weapons, cosmetic skins, and more. While loot boxes are often minted individually, opening a loot box grants players multiple assets simultaneously, making it an ideal scenario for leveraging the efficiency of batch minting functions.
tip

The amount of gas saving by using batch minting techniques can be further increased by strategically delaying NFT minting to enable the minting of larger batches. For example, delaying mint requests by 1-5 minutes can result in substantial 60-80% gas savings in specific contexts.

tip

Immutable provides a suite of preset contracts to to cover a game studio's diverse needs.

Immutable's recommended ERC721 Preset is the recommended choice for game studios due to its extensive feature set. Deployed contracts are immutable, and asset migrations to new more feature rich collections can be costly. It's advised to opt for the ERC721 preset even if certain features are unused. This approach allows future utilization as the game evolves.

Immutable's recommended ERC721 Preset conveniently incorporates multiple batch minting solutions within a single contract. This empowers game studios to select the batch minting strategy to the specific scenario while maintaining assets within the same collection.

For batch minting use cases where the token ID must be specified the mintBatch() function can be used:

  • Migrating NFTs across chains/collections.
  • Player initiated crafting requests. As the player is requesting the mints, specifying the Token ID upfront can be beneficial for preparing metadata and error handling.
  • Token ID of NFT must be a specific number.

For use cases where gas efficiency is of primary importance, mintBatchByQuantity() can be used (minting 10 NFTs to a single address with mintBatchByQuantity() can be 65% more gas efficent than single mint requests):

  • Batch minting with multiple NFTs per wallet (e.g., 500 NFTs to 5 addresses).
  • Primary Sales: Selling 10 NFTs to a player in a primary sale.
  • New Player Assets: Providing 20 basic NFTs to new players.
  • Event Giveaways: Awarding many assets to many players as a result of player's performance in set tasks.
  • Player Engagement: Granting players 2 or more NFTs upon level or match completion.
  • Loot boxes: Opening loot boxes to receive multiple items (e.g. 5 items) to a single wallet.

Immutable's recommended ERC721 Preset offers game studios unparalleled flexibility in a single contract by reserving Token IDs for each minting function. Those below 2^128, which is the mintBatchByQuantityThreshold which is up to and including 340,282,366,920,938,463,463,374,607,431,768,211,455, belong to mintBatch() or mint(), while those equal to or greater than the mintBatchByQuantityThreshold are reserved for mintBatchByQuantity().

tip

Partner's who are able to deploy their own smart contracts on Immutable's zkEVM will be able to override mintBatchByQuantityThreshold method to adjust the Token ID threshold reserved for mintBatchByQuantity(). This can be achieved through deploying the below contract.

ImmutableERC721Contract.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import '@imtbl/zkevm-contracts/contracts/token/erc721/preset/ImmutableERC721.sol';

contract MyERC721Contract is ImmutableERC721 {
constructor(
address owner,
string memory name,
string memory symbol,
string memory baseURI,
string memory contractURI,
address operatorAllowlist,
address receiver,
uint96 feeNumerator
)
ImmutableERC721(
owner,
name,
symbol,
baseURI,
contractURI,
operatorAllowlist,
receiver,
feeNumerator
)
{}

// Bulk mint threshold will start from ID 1
function mintBatchByQuantityThreshold() public pure override returns (uint256) {
return 1;
}
}
StandardPreset

Using Immutable's preset contracts to mintBatch()

tip

Immutable's preset contracts enables one to mint multiple NFTs to multiple wallets. The mintBatch() function does not need to be called separately for each receiving wallet. This function is most gas efficent when minting 1-2 NFTs to each wallet in a batch.

Immutable's recommended ERC721 Preset and the ERC721 By-ID Only Preset feature a mintBatch() function, enabling the caller to efficiently mint a batch of assets with caller-specified Token IDs.

To mint a batch of assets perform the following steps:

  1. Get some test $IMX.

    To get test $IMX you will need to create a Immutable Hub account and go to this faucet. Enter your wallet's address in the text field requesting it and then click the Receive Test-IMX button. It may take a few minutes for the test $IMX to arrive, after which, your test $IMX balance will be visible in your wallet.

  2. Deploy Immutable's ERC721 preset contract.

    • Technical details of the contract can be found here
    • A step-by-step guide of how to deploy a ERC721 contract can be found here.
  3. Ensure you have enough test $IMX in your wallet to deploy the intended batch of NFTs.

  4. Create a file called mintNFTsByID.ts

  5. Add the following code to your file with the following variables:

    • CONTRACT_ADDRESS - The address of the deployed ERC721 contract
    • PRIVATE_KEY - The private key of the account used for signing the mint request
    • ACCOUNT_ADDRESS1 - The first address of the wallet that will own the NFTs being minted in the batch
    • ACCOUNT_ADDRESS2 - The second address of the wallet that will own the NFTs being minted in the batch
    • TOKEN_ID1 - A free token ID in the collection that is less than 18,446,744,073,709,551,616
    • TOKEN_ID2 - A free token ID in the collection that is less than 18,446,744,073,709,551,616
    • TOKEN_ID3 - A free token ID in the collection that is less than 18,446,744,073,709,551,616
    • TOKEN_ID4 - A free token ID in the collection that is less than 18,446,744,073,709,551,616
mintNFTsByID.ts
import { getDefaultProvider, Wallet } from 'ethers'; // ethers v5
import { Provider, TransactionResponse } from '@ethersproject/providers'; // ethers v5
import { ERC721Client } from '@imtbl/contracts';

const CONTRACT_ADDRESS = 'CONTRACT_ADDRESS';
const PRIVATE_KEY = 'PRIVATE_KEY';
const TOKEN_ID_1 = 0;
const TOKEN_ID_2 = 0;
const TOKEN_ID_3 = 0;
const TOKEN_ID_4 = 0;
const ACCOUNT_ADDRESS_1 = 'ACCOUNT_ADDRESS_1';
const ACCOUNT_ADDRESS_2 = 'ACCOUNT_ADDRESS_2';
const provider = getDefaultProvider('https://rpc.testnet.immutable.com');

const mint = async (provider: Provider): Promise<TransactionResponse> => {
// Bound contract instance
const contract = new ERC721Client(CONTRACT_ADDRESS);
// The wallet of the intended signer of the mint request
const wallet = new Wallet(PRIVATE_KEY, provider);
// We can use the read function hasRole to check if the intended signer
// has sufficient permissions to mint before we send the transaction
const minterRole = await contract.MINTER_ROLE(provider);
const hasMinterRole = await contract.hasRole(
provider,
minterRole,
wallet.address
);

if (!hasMinterRole) {
// Handle scenario without permissions...
console.log('Account doesnt have permissions to mint.');
return Promise.reject(
new Error('Account doesnt have permissions to mint.')
);
}

// Construct the mint requests
const requests = [
{
to: ACCOUNT_ADDRESS_1,
tokenIds: [TOKEN_ID_1, TOKEN_ID_2],
},
{
to: ACCOUNT_ADDRESS_2,
tokenIds: [TOKEN_ID_3, TOKEN_ID_4],
},
];

// Rather than be executed directly, contract write functions on the SDK client are returned
// as populated transactions so that users can implement their own transaction signing logic.
const populatedTransaction = await contract.populateMintBatch(requests);
const result = await wallet.sendTransaction(populatedTransaction);
console.log(result); // To get the TransactionResponse value
return result;
};

mint(provider);
  1. Run the following script:
 ./node_modules/.bin/ts-node mintNFTsByID.ts

Using Immutable's preset contracts to mintBatchByQuantity()

note

The mintBatchByQuantity() method is most gas efficent when minting more than 2 NFTs to each wallet.

Immutable's recommended ERC721 Preset has the mintBatchByQuantity() function that allows the caller to mint a sequence of assets with Token IDs equal or above the mintBatchByQuantityThreshold. As the Token IDs are generated by the collection; it cannot be specified pre-mint.

To mint a batch of assets perform the following steps:

  1. Get some test $IMX.

    To get test $IMX you will need to create a Immutable Hub account and go to this faucet. Enter your wallet's address in the text field requesting it and then click the Receive Test-IMX button. It may take a few minutes for the test $IMX to arrive, after which, your test $IMX balance will be visible in your wallet.

  2. Deploy Immutable's ERC721 preset contract.

    • Technical details of the contract can be found here
    • A step-by-step guide of how to deploy a ERC721 contract can be found here.
  3. Ensure you have enough test $IMX in your wallet to deploy the intended batch of NFTs.

  4. Create a file called mintNFTsByQuantity.ts

  5. Add the following code to your file with the following variables:

    • CONTRACT_ADDRESS - The address of the deployed ERC721 contract
    • PRIVATE_KEY - The private key of the account used for signing the mint request
    • ACCOUNT_ADDRESS1 - The first address of the wallet that will own the NFTs being minted in the batch
    • ACCOUNT_ADDRESS2 - The second address of the wallet that will own the NFTs being minted in the batch
mintNFTsByQuantity.ts
import { getDefaultProvider, Wallet } from 'ethers'; // ethers v5
import { Provider, TransactionResponse } from '@ethersproject/providers'; // ethers v5
import { ERC721Client } from '@imtbl/contracts';

const CONTRACT_ADDRESS = 'CONTRACT_ADDRESS';
const PRIVATE_KEY = 'PRIVATE_KEY';
const ACCOUNT_ADDRESS_1 = 'ACCOUNT_ADDRESS_1';
const ACCOUNT_ADDRESS_2 = 'ACCOUNT_ADDRESS_2';

// Specify who we want to receive the minted token
const provider = getDefaultProvider('https://rpc.testnet.immutable.com');

const mint = async (provider: Provider): Promise<TransactionResponse> => {
// Bound contract instance
const contract = new ERC721Client(CONTRACT_ADDRESS);
// The wallet of the intended signer of the mint request
const wallet = new Wallet(PRIVATE_KEY, provider);
// We can use the read function hasRole to check if the intended signer
// has sufficient permissions to mint before we send the transaction
const minterRole = await contract.MINTER_ROLE(provider);
const hasMinterRole = await contract.hasRole(
provider,
minterRole,
wallet.address
);

if (!hasMinterRole) {
// Handle scenario without permissions...
console.log('Account doesnt have permissions to mint.');
return Promise.reject(
new Error('Account doesnt have permissions to mint.')
);
}

const mints = [
{
to: ACCOUNT_ADDRESS_1,
quantity: 3,
},
{
to: ACCOUNT_ADDRESS_2,
quantity: 3,
},
];

// Rather than be executed directly, contract write functions on the SDK client are returned
// as populated transactions so that users can implement their own transaction signing logic.
const populatedTransaction = await contract.populateMintBatchByQuantity(
mints
);
const result = await wallet.sendTransaction(populatedTransaction);
console.log(result); // To get the TransactionResponse value
return result;
};

mint(provider);
  1. Run the following script:
 ./node_modules/.bin/ts-node mintNFTsByQuantity.ts

Verify successful mints

tip

Blockscout's Immutable zkEVM testnet explorer can be used to verify your contract has been deployed to the network.

Entering the CONTRACT_ADDRESS in the search bar will allow you to view transactions by collection. The mint requests you generated will produce a Contract Call record.

Using the below script you can verify the minting request was successful.

  1. Create a file called reviewTransaction.ts
  2. Add the following code to your file with the following variable:
    • CONTRACT_ADDRESS - The address of the deployed ERC721 contract
import { config as immutableConfig, blockchainData } from '@imtbl/sdk';

const CONTRACT_ADDRESS = 'CONTRACT_ADDRESS'; // The address of the contract you deployed
const PUBLISHABLE_KEY = 'YOUR_PUBLISHABLE_KEY'; // Replace with your Publishable Key from the Immutable Hub

const config: blockchainData.BlockchainDataModuleConfiguration = {
baseConfig: {
environment: immutableConfig.Environment.SANDBOX,
publishableKey: PUBLISHABLE_KEY,
},
};

const client = new blockchainData.BlockchainData(config);

async function getData() {
try {
const response = await client.listActivities({
chainName: 'imtbl-zkevm-testnet',
contractAddress: CONTRACT_ADDRESS,
});

for (const result in response.result) {
console.log(result);
}
return response.result;
} catch (error) {
console.error(error);
}
}

getData();
  1. Run the following script:
 ./node_modules/.bin/ts-node reviewTransaction.ts

How else can consolidating actions into batches save me gas?

Immutable's ERC721 preset contract includes functionality for transferring or burning multiple NFTs in one transaction. Just like batch minting, consolidating multiple on-chain actions into a single transaction reduces gas fees for the initiator due to efficiencies achieved via economies of scale. Check out our transfer and burn tutorials for more details on how to transfer and burn in batches to realize these gas savings.