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 refers to the process of creating multiple non-fungible tokens (NFTs) or semi-fungible tokens (SFTs) in a single transaction on the blockchain. Traditionally, minting each token_id requires a separate transaction, which can be time-consuming and costly in terms of gas fees. Batch minting streamlines this process by allowing the simultaneous creation of multiple token_ids in one transaction, thereby reducing both the number of transactions and the associated costs.

This approach is especially beneficial for game studios that need to mint large quantities of tokens at once, such as when launching a new collection, distributing rewards, or provisioning in-game assets to multiple users. By batching these operations, studios can minimize the costs and time related to gas fees and transaction confirmations, leading to a more efficient and scalable process.

tip

Batch minting functions require all assets within a batch to belong to the same collection.

How Does Batch Minting Save Gas?

Immutable’s batch minting techniques are designed to optimize gas usage by reducing the computational and storage costs associated with blockchain transactions.

ERC721 & ERC1155: Reducing Transactional Overheads

Every blockchain transaction incurs computational and storage costs. Batch minting reduces these costs by consolidating multiple minting actions into a single transaction, which significantly lowers gas fees compared to minting each asset individually. This method leverages economies of scale, allowing the creation of multiple assets with different token_ids at a lower collective gas fee.

See below for the batch functions offered by Immutable's preset contracts designed to reduce transactional overheads.

ERC1155: Efficient Blockchain Storage for SFTs

In Web3 gaming, where assets are often semi-fungible, ERC1155 collections are typically more suitable. In scenarios where a player might hold multiple identical assets, ERC1155 consumes less storage because a single token_id can represent more than a single asset.

For example, if a player owns five identical swords, ERC1155 would record this with one token_id and a quantity of five, whereas ERC721 would require five separate entries, each with its own token_id. This makes ERC1155 more gas-efficient for handling semi-fungible assets in games.

ERC721: Minimizing Blockchain Data Storage

Each NFT minting operation adds data to the blockchain, which incurs gas costs. The mintBatchByQuantity() function is designed to require less on-chain data storage, so requires less gas.

For example, traditional ERC721 contracts store data for each NFT's identity and ownership. In a standard ERC721 mint operation, each NFT's token_id and account_address (owner's wallet address) must be recorded on-chain.

Immutable's recommended ERC721 Preset contains the mintBatchByQuantity() function, which reduces blockchain data usage by attributing one account_address to multiple NFTs minted to the same wallet. In this method, the first NFT of a batch has a defined account_address, while subsequent NFTs in the batch show a null value for account_address, indicating they belong to the same owner as the first. The next populated account_address signals the beginning of a new batch of NFTs owned by a different wallet. This approach reduces on-chain data and, consequently, lowers gas costs.

This structure is illustrated below:

BulkMintDataSaving
note

While the explanation above conceptually illustrates how the data structure works, Immutable's recommended ERC721 Preset actually uses a more efficient method involving bitmaps and a de Bruijn sequence for locating account_address data. This approach is optimized beyond the sequential checking of previous NFTs.

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 assets with different token_id incurs lower gas costs than individually minting each asset in separate transactions.

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/SFTs, 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/SFT minting to enable the minting of larger batches.

How to batch mint

A game can batch mint either via a direct function call or through Immutable's Minting API.

Minting by function call is the most gas-efficient method and will be discussed in more detail in this section.

Minting via API is significantly easier to execute at scale but comes with additional cost overheads for the convenience (the Minting API contract requires additional gas for the services it performs). Currently, all Minting API requests are gas-free, though this policy may change in the future.

Immutable's ERC1155 Preset Contract

ERC1155 standard contract includes batch mining with the safeMintBatch() function.

To use this function, the mint requestor must include a token_id to specify the new token they wish to create, or add additional tokens to an existing token_id.

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 ERC1155 preset contract.

    • Technical details of the contract can be found here
    • Deploy the ERC1155 contract manually via the command line, or use Immutable's Hub(https://hub.immutable.com/) to deploy a ERC1155 collection.
  3. Ensure you have enough test $IMX in your wallet to deploy the intended batch of SFTs.

  4. Create a file called mintBatch1155.ts

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

    • CONTRACT_ADDRESS - The address of the deployed ERC1155 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 SFTs being minted in the batch
    • ACCOUNT_ADDRESS2 - The second address of the wallet that will own the SFTs being minted in the batch
    • TOKEN_ID1 - A free token ID in the collection
    • TOKEN_ID2 - A free token ID in the collection
💡info

Find a more detailed explanation about how to interact with your deployed collection here.

mintBatch1155.ts
import { getContract, http, createWalletClient, defineChain, ByteArray, toBytes } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { ImmutableERC1155Abi } from '@imtbl/contracts';

const PRIVATE_KEY = 'YOUR_PRIVATE_KEY'; // should be read from environment variable
const CONTRACT_ADDRESS = '0xYOUR_CONTRACT_ADDRESS'; // should be of type `0x${string}`
const RECEIVER = '0xRECEIVER_ADDRESS'; // should be of type `0x${string}`
const TOKEN_IDS: bigint[] = [BigInt(1), BigInt(2)]; // should be of type BigInt
const TOKEN_VALUES: bigint[] = [BigInt(1), BigInt(1)]; // should be of type BigInt
const DATA = '0x'; // should be of type `0x${string}`

const immutableTestnet = defineChain({
id: 13473,
name: 'imtbl-zkevm-testnet',
nativeCurrency: { name: 'IMX', symbol: 'IMX', decimals: 18 },
rpcUrls: {
default: {
http: ['https://rpc.testnet.immutable.com'],
},
},
});

const walletClient = createWalletClient({
chain: immutableTestnet,
transport: http(),
account: privateKeyToAccount(`0x${PRIVATE_KEY}`),
});

const contract = getContract({
address: CONTRACT_ADDRESS,
abi: ImmutableERC1155Abi,
client: walletClient,
});

const safeMintBatch = async (): Promise<void> => {
await contract.write.safeMintBatch([RECEIVER, TOKEN_IDS, TOKEN_VALUES, DATA]);
};

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

Immutable's ERC721 Preset Contract

Immutable's recommended ERC721 Preset offers a versatile solution for batch minting, allowing game studios to choose the most suitable minting strategy for each use-case, while keeping all assets within the same collection. The contract contains the following batch minting functions:

  • mintBatch(): Facilitates gas-efficient batch minting where the minter can specify token_id for new assets.
  • mintBatchByQuantity(): Enables sequential batch minting where the system automatically generates the next token_id, offering the most gas-efficient option.

To support these batch minting functions, the contract reserves distinct token_id ranges for each function. By default, token_ids below 2^128 (up to 340,282,366,920,938,463,463,374,607,431,768,211,455) are reserved for the mintBatch() or mint() functions. The mintBatchByQuantity() function uses token_ids higher than this threshold. The attribute mintBatchByQuantityThreshold controls the token_id range reserved for each minting function.

StandardPreset

Batch Minting with Immutable's ERC721 Preset Contract

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.

Good For
  • Migrations: Ideal for migrating NFTs across chains or collections where the token_id needs to remain consistent.
  • Crafting: Useful for player-initiated crafting requests, where specifying the token_id upfront can aid in preparing metadata and managing error handling.
  • Token ID Categorization: Necessary when the token_id must represent specific asset types (e.g., Token ID: 1-10,000 = Swords; Token ID: 10,001-20,000 = Shields).
Supported Preset Contracts
How to Batch Mint with mintBatch()

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
💡info

Find a more detailed explanation about how to interact with your deployed collection here.

mintNFTsByID.ts
import { getContract, http, createWalletClient, defineChain } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { ImmutableERC721Abi } from '@imtbl/contracts';

const PRIVATE_KEY = 'YOUR_PRIVATE_KEY'; // should be read from environment variable
const CONTRACT_ADDRESS = '0xYOUR_CONTRACT_ADDRESS'; // should be of type `0x${string}`
const TOKEN_ID_1 = BigInt(1);
const TOKEN_ID_2 = BigInt(2);
const TOKEN_ID_3 = BigInt(3);
const TOKEN_ID_4 = BigInt(4);
const ACCOUNT_ADDRESS_1: `0x${string}` = '0xACCOUNT_ADDRESS_1'; // should be of type `0x${string}`
const ACCOUNT_ADDRESS_2: `0x${string}` = '0xACCOUNT_ADDRESS_2'; // should be of type `0x${string}`

const immutableTestnet = defineChain({
id: 13473,
name: 'imtbl-zkevm-testnet',
nativeCurrency: { name: 'IMX', symbol: 'IMX', decimals: 18 },
rpcUrls: {
default: {
http: ['https://rpc.testnet.immutable.com'],
},
},
});

const walletClient = createWalletClient({
chain: immutableTestnet,
transport: http(),
account: privateKeyToAccount(`0x${PRIVATE_KEY}`),
});

// Bound contract instance
const contract = getContract({
address: CONTRACT_ADDRESS,
abi: ImmutableERC721Abi,
client: walletClient,
});

const mint = async (): Promise<string> => {
// 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.read.MINTER_ROLE();

const hasMinterRole = await contract.read.hasRole([
minterRole,
walletClient.account.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],
},
];

const txHash = await contract.write.mintBatch([requests]);

console.log(`txHash: ${txHash}`);
return txHash;
};

mint();
  1. Run the following script:
 ./node_modules/.bin/ts-node mintNFTsByID.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

Other Batch Transactions

Batching other collection transactions can also lead to significant gas savings, similar to those achieved with batch minting.

Immutable's ERC721 & ERC1155 preset contracts include built-in functionality for transferring or burning multiple assets within a single transaction. This approach not only reduces gas costs but also streamlines operations for game studios managing large numbers of assets.

For detailed instructions on how to perform these operations in batches and maximize gas efficiency, check out our transfer and burn tutorials.