Very high gas fees for what seems like a straight forward and simple contract

Hi everyone ... I am trying to understand why the gas fees for this contract are very high ... $26 to mint one NFT as opposed to this mint service that is only $7 - https://launchpad.heymint.xyz/mint/rebelapeyeezyclub

This is my smart contract deployed with Remix with optimization enabled - 200. I verified it on Etherscan as a single file after flattening it using npx hardhat flatten.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract TestContract is ERC721, Ownable {
    // Constants
    uint256 public constant TOTAL_SUPPLY = 15200;
    uint256 public constant MINT_PRICE = 0.002 ether;
    uint256 public constant MAX_PUBLIC_MINT = 20;

    string public _provenanceHash = "";
    string public _baseURL;

    uint256 private currentTokenId;

    constructor(address initialAddress)
        Ownable(initialAddress)
        ERC721("TEST", "TST")
    {}

    function mintBatch(address recipient, uint256 count) public payable {
        require(
            count > 0 && count <= MAX_PUBLIC_MINT,
            "Maximum public mint reached"
        );
        require(
            msg.value == MINT_PRICE * count,
            "Transaction value did not equal the mint price"
        );

        require(
            currentTokenId + count <= TOTAL_SUPPLY,
            "Maximum supply reached"
        );

        if (count == 1) {
            _safeMint(recipient, currentTokenId);
            currentTokenId += 1;
        } else {
            for (uint256 i = 0; i < count; i++) {
                _safeMint(recipient, currentTokenId);
                currentTokenId += 1;
            }
        }
    }

    function setBaseURL(string memory baseURI) public onlyOwner {
        _baseURL = baseURI;
    }

    function _baseURI() internal view override returns (string memory) {
        return _baseURL;
    }

    function totalSupply() public view returns (uint256) {
        return currentTokenId;
    }

    function setProvenanceHash(string memory provenanceHash) public onlyOwner {
        _provenanceHash = provenanceHash;
    }

    function withdraw() public onlyOwner {
        uint256 balance = address(this).balance;
        Address.sendValue(payable(msg.sender), balance);
    }
}

This is how the mint function is being called from the frontend

const mintCharacter = async () => {
    try {
      const { ethereum } = window;

      if (ethereum) {
        const provider = new ethers.providers.Web3Provider(ethereum);
        const signer = provider.getSigner();

        const nftContract = new ethers.Contract(
          nftContractAddress,
          Contract.abi,
          signer
        );

        const options = {
          value: ethers.utils.parseEther(String(0.002 * total)),
          gasLimit: 500000,
        };

        const nftTx = await nftContract.mintBatch(
          nftContractAddress,
          total,
          options
        );

        ...more code

      } else {
        console.log("Ethereum object doesn't exist!");
      }
    } catch (error) {
      console.log("Error minting NFT", error);
    }
  };

Well, there are at least 3 pieces of information missing here:

  1. The gas-price specified in the transaction executed on your contract
  2. The gas-price specified in the transaction executed on heymint's contract
  3. The source code of heymint's contract

That being said, if we assume the following:

  1. The same gas-price is specified in both transactions
  2. The source code of heymint's contract is identical to that of your contract

Then 2 things which may increase the gas consumption are:

  1. The very first mint is generally more expensive than every other mint which follows it, because the contract function changes the storage variable currentTokenId from zero to non-zero
  2. The very first mint for a given recipient is generally more expensive than every other mint for the same recipient which follows it, because the contract function changes the storage variable _balances[recipient] from zero to non-zero

And changing a storage variable from zero to non-zero consumes a lot more gas than changing a storage variable from non-zero to non-zero (at the time of writing this, 22K gas vs 5K gas respectively).

Presumably, you are comparing a first mint on your contract vs a non-first mint on heymint's contract.