ETH Price: $2,508.40 (+0.27%)

Token

Wormhole Community Wassies (WCW)
 

Overview

Max Total Supply

4,468 WCW

Holders

1,400

Market

Volume (24H)

N/A

Min Price (24H)

N/A

Max Price (24H)

N/A
Balance
1 WCW
0x9d92e0378225c5eaf88069f307c44cda08622653
Loading...
Loading
Loading...
Loading
Loading...
Loading

Click here to update the token information / general information
# Exchange Pair Price  24H Volume % Volume

Minimal Proxy Contract for 0x2fe586e0ba94f3e7a4b1377bd20adcec69c1a048

Contract Name:
CommunityCollection

Compiler Version
v0.8.24+commit.e11b9ed9

Optimization Enabled:
Yes with 200 runs

Other Settings:
paris EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 7 : CommunityCollection.sol
// SPDX-License-Identifier: MIT

/**
 * @title CommunityCollection.sol. NFT collection for input controlled token types, in this case using
 * a merkle tree.
 *
 * @author omnus (https://omn.us) for bywassies (https://bywassies.com)
 */

pragma solidity 0.8.24;

// ERC721CC is a fork of ERC721A from Chiru labs with additional features.
import {ERC721CC} from "../ERC721CC/ERC721CC.sol";
// The CommunityCollection interface.
import {ICommunityCollection} from "./ICommunityCollection.sol";
// OZs ownable implementation.
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
// The MerkleProof library provides methods to validate leaves and proofs against the merkle root.
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

contract CommunityCollection is ERC721CC, ICommunityCollection, Ownable {
  // bytes14 confirmation required on certain state changing method to avoid being called
  // be accident.
  bytes14 internal constant CONFIRMATION_BYTES = 0x6C6F636B6564666F726576657221;

  // We store that addresses have minted, ensuring that any address can only mint once.
  // Later versions can potentially including allowances greater than one and tracking
  // against that allowance.
  mapping(address => mapping(uint256 => bool)) internal addressHasMinted;

  // The merkle root is set on the initialise and can be updated by the owner as required.
  bytes32 internal merkleRoot;

  /**
   * --------------
   * INITIALISATION
   * --------------
   */

  /**
   * @dev constructor
   *
   * The constructor is only called when the contract template is deployed. It is NOT called
   * when clones are instantiated. For this reason all setup logic must be executed in the `initialise`
   * method.
   */
  constructor() Ownable(msg.sender) {
    renounceOwnership();
  }

  /**
   * @dev initialise
   *
   * This method is called by the factory when creating a clone. It handles the initial setup of the
   * ERC-721 collection.
   *
   * This method also receives the bytes argument `initialArgs_`. This allows subsequent versions of this
   * contract to require new initialise arguments without the factory needing to change in order to supply
   * these arguments.
   *
   * @param name_ The name of this collection, typicaly a short string
   * @param symbol_ The symbol for this collection, commonly a short string in capital letters.
   * @param baseURI_ The base URI for this collection. URIs will be formed from this string, plus the URI suffix.
   * @param switches_ Array of collection control booleans:
   *        [0] uniqueMetadata: If the collection has unique metadata for each NFT. If this is true the baseURI
   *            is suffixed. If false the baseURI is returned for all.
   *        [1] transferable: Is this collection transferable? If false this collection is 'soulbound'
   *        [2] burnable: Is this collection burnable? If false this collection cannot be burned.
   * @param maxSupply_ The maximum number of tokens that can be minted in this collection. 0 = unlimited.
   * @param initialArgs_ A bytes parameter than can contain further, as of yet undefined, parameters.
   */
  function initialise(
    string calldata name_,
    string calldata symbol_,
    string calldata baseURI_,
    uint256 maxSupply_,
    bool[] calldata switches_,
    bytes calldata initialArgs_
  ) external {
    _initialiseERC721CC(
      name_,
      symbol_,
      baseURI_,
      maxSupply_,
      switches_,
      initialArgs_
    );
    // CommunityCollections are created via the CommunityCollectionFactory.
    // We want to initialise the owner to the caller of the CommunityCollectionFactory.
    _transferOwnership(tx.origin);
  }

  /**
   * ---------
   * MODIFIERS
   * ---------
   */

  /**
   * @dev onlyWhenURIUnlocked
   *
   * This modifier will revert if the URI is locked
   */
  modifier onlyWhenURIUnlocked() {
    if (lockedURI) {
      revert("URI is locked");
    }
    _;
  }

  /**
   * @dev onlyWhenMintingUnlocked
   *
   * This modifier will revert if minting is locked
   */
  modifier onlyWhenMintingUnlocked() {
    if (lockedMinting) {
      revert("Minting is locked forever");
    }
    _;
  }

  /**
   * -------------------
   * VIEW METHOD GETTERS
   * -------------------
   */

  /**
   * @dev getMerkleRoot
   *
   * External function to return the current merkle root
   */
  function getMerkleRoot() external view returns (bytes32) {
    return (merkleRoot);
  }

  /**
   * @dev getAddressHasMinted
   *
   * External function to return if the address has minted on the queried collection.
   */
  function getAddressHasMinted(
    address minter_,
    uint256 tokenTypeId_
  ) external view returns (bool) {
    return addressHasMinted[minter_][tokenTypeId_];
  }

  /**
   * @dev getAllTokenTypes
   *
   * External function to return full array of token types
   */
  function getAllTokenTypes() external view returns (uint16[] memory) {
    return (tokenIdToTypeId);
  }

  /**
   * -------
   * UPDATES
   * -------
   */

  /**
   * @dev updateMerkleRoot
   *
   * An onlyAdmin method that allows the owner to update the root for the merkle tree.
   *
   * @param newMerkleRoot_ The new bytes32 merkle root.
   */
  function updateMerkleRoot(bytes32 newMerkleRoot_) external onlyOwner {
    merkleRoot = newMerkleRoot_;
    emit MerkleRootUpdated(newMerkleRoot_);
  }

  /**
   * @dev updateUniqueMetadata
   *
   * onlyOwner method to update a the unique metadata boolean
   *
   * @param uniqueMetadata_: whether this collection has unique metadata (or not)
   */
  function updateUniqueMetadata(
    bool uniqueMetadata_
  ) external onlyOwner onlyWhenURIUnlocked {
    uniqueMetadata = uniqueMetadata_;
    emit UniqueMetadataBoolUpdated();
  }

  /**
   * @dev updateBaseURI
   *
   * onlyOwner method to update an unlocked URI
   *
   * @param uri_: the new URI
   */
  function updateBaseURI(
    string calldata uri_
  ) external onlyOwner onlyWhenURIUnlocked {
    string memory oldURI = baseURI;
    baseURI = uri_;
    emit URIUpdated(oldURI, uri_);
  }

  /**
   * @dev updateURISuffixes
   *
   * onlyOwner method to update an unlocked URI suffix(es)
   *
   * @param uriSuffixes_: the new URI suffixes
   */
  function updateURISuffixes(
    URISuffixes[] calldata uriSuffixes_
  ) external onlyOwner onlyWhenURIUnlocked {
    for (uint256 i = 0; i < uriSuffixes_.length; ) {
      string memory oldSuffixURI = uriSuffix[uriSuffixes_[i].tokenTypeId];
      uriSuffix[uriSuffixes_[i].tokenTypeId] = uriSuffixes_[i].suffix;
      emit URISuffixUpdated(oldSuffixURI, uriSuffixes_[i].suffix);
      unchecked {
        i++;
      }
    }
  }

  /**
   * @dev lockURI
   *
   * onlyOwner method to lock the URI
   *
   * @param confirm_ confirmation value to prevent erroneous locking
   */
  function lockURI(bytes14 confirm_) external onlyOwner {
    if (confirm_ != CONFIRMATION_BYTES) {
      revert("Incorrect confirmation");
    }
    lockedURI = true;
    emit URILocked();
  }

  /**
   * @dev lockMinting
   *
   * onlyOwner method to lock minting forever
   *
   * @param confirm_ confirmation value to prevent erroneous locking
   */
  function lockMinting(bytes14 confirm_) external onlyOwner {
    if (confirm_ != CONFIRMATION_BYTES) {
      revert("Incorrect confirmation");
    }
    lockedMinting = true;
    emit MintingLocked();
  }

  /**
   * ----------------
   * TOKEN OPERATIONS
   * ----------------
   */

  /**
   * @dev communityMint
   *
   * Validate caller eligiblity and mint
   *
   * @param mintRequests_ An array of mint requests. These include the
   * token type identifier and the corresponding proof.
   */
  function communityMint(
    MintRequest[] calldata mintRequests_
  ) external onlyWhenMintingUnlocked {
    if (mintRequests_.length == 0) {
      revert("Must mint something");
    }

    for (uint256 i = 0; i < mintRequests_.length; ) {
      _addressHasMintedCheck(msg.sender, mintRequests_[i].tokenTypeId);

      _merkleTreeCheck(
        mintRequests_[i].proof,
        keccak256(abi.encodePacked(msg.sender, mintRequests_[i].tokenTypeId))
      );

      _recordAddressMinted(msg.sender, mintRequests_[i].tokenTypeId);

      unchecked {
        i++;
      }
    }

    // Pass all the requests to mint, so we mint in one batch and take advantage of ERC721A:
    _mint(msg.sender, mintRequests_.length, mintRequests_);
  }

  /**
   * @dev _addressHasMintedCheck
   *
   * An internal method to check if an address has already minted. It will revert if it has.
   *
   * @param minter_ The minter address being checked.
   * @param tokenTypeId_ The token type being checked.
   */
  function _addressHasMintedCheck(
    address minter_,
    uint256 tokenTypeId_
  ) internal view {
    if (addressHasMinted[minter_][tokenTypeId_]) {
      revert("Address has already minted");
    }
  }

  /**
   * @dev _merkleTreeCheck
   *
   * An internal method to check if a leaf hash and proof pass the merkle check. It will revert if it does not.
   *
   * @param proof_ The provided proof
   * @param leafHash_ The leaf hash being checked.
   */
  function _merkleTreeCheck(
    bytes32[] calldata proof_,
    bytes32 leafHash_
  ) internal view {
    // Cannot mint using a merkle tree if we have a blank root:
    if (merkleRoot == bytes32(0)) {
      revert("No root set");
    }

    if (!addressIsInMerkleTree(proof_, leafHash_)) {
      revert("Address not in the list");
    }
  }

  /**
   * @dev addressIsInMerkleTree
   *
   * An public method to check if an address and proof pass the merkle check. It returns this as a bool.
   *
   * @param proof_ The provided proof
   * @param leafHash_ The leaf hash being checked.
   */
  function addressIsInMerkleTree(
    bytes32[] calldata proof_,
    bytes32 leafHash_
  ) public view returns (bool) {
    return (MerkleProof.verify(proof_, merkleRoot, leafHash_));
  }

  /**
   * @dev _recordAddressMinted
   *
   * An internal method to update the status of an address in the `addressHasMinted` mapping.
   *
   * @param minter_ The address which has minted.
   * @param tokenTypeId_ The token type being checked.
   */
  function _recordAddressMinted(
    address minter_,
    uint256 tokenTypeId_
  ) internal {
    addressHasMinted[minter_][tokenTypeId_] = true;
  }

  /**
   * @dev fixedMint
   *
   * Owner only fixed amount mint function
   *
   * @param mintRequests_ An array of mint requests.
   */
  function fixedMint(
    MintRequest[] calldata mintRequests_
  ) external onlyWhenMintingUnlocked onlyOwner {
    if (mintRequests_.length == 0) {
      revert("Must mint something");
    }

    // Pass all the requests to mint, so we mint in one batch and take advantage of ERC721A:
    _mint(msg.sender, mintRequests_.length, mintRequests_);
  }

  /**
   * @dev burn
   *
   * Burn tokens!
   *
   * @param tokenId_: the tokenId to burn (note - owner / auth checks performed in ERC721Sub)
   */
  function burn(uint256 tokenId_) external {
    _burn(tokenId_, true);
  }

  /**
   * --------------------------
   * NO RANDOM ETH, NO FALLBACK
   * --------------------------
   */

  receive() external payable onlyOwner {}

  fallback() external {
    revert("No fallback here");
  }
}

File 2 of 7 : Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

File 3 of 7 : Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

File 4 of 7 : MerkleProof.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MerkleProof.sol)

pragma solidity ^0.8.20;

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * The tree and the proofs can be generated using our
 * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
 * You will find a quickstart guide in the readme.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the Merkle tree could be reinterpreted as a leaf value.
 * OpenZeppelin's JavaScript library generates Merkle trees that are safe
 * against this attack out of the box.
 */
library MerkleProof {
    /**
     *@dev The multiproof provided is not valid.
     */
    error MerkleProofInvalidMultiproof();

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     */
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Calldata version of {verify}
     */
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProofCalldata(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leafs & pre-images are assumed to be sorted.
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Calldata version of {processProof}
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Calldata version of {multiProofVerify}
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proofLen != totalHashes + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            if (proofPos != proofLen) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[totalHashes - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Calldata version of {processMultiProof}.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proofLen != totalHashes + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            if (proofPos != proofLen) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[totalHashes - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Sorts the pair (a, b) and hashes the result.
     */
    function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
    }

    /**
     * @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
     */
    function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}

File 5 of 7 : ICommunityCollection.sol
// SPDX-License-Identifier: MIT

/**
 * @title ICommunityCollection.sol. NFT collection for input controlled token types, in this case using
 * a merkle tree.
 *
 * @author omnus (https://omn.us) for bywassies (https://bywassies.com)
 */

pragma solidity 0.8.24;

// IERC721CC is a fork of IERC721A from Chiru labs with additional features
import {IERC721CC} from "../ERC721CC/ERC721CC.sol";

interface ICommunityCollection is IERC721CC {
  struct URISuffixes {
    uint256 tokenTypeId;
    string suffix;
  }

  event MerkleRootUpdated(bytes32 newMerkleRoot);

  event MintingLocked();

  event UniqueMetadataBoolUpdated();

  event URIUpdated(string oldURI, string newURI);

  event URISuffixUpdated(string oldURISuffix, string newURISuffix);

  event URILocked();

  /**
   * @dev initialise
   *
   * This method is called by the factory when creating a clone. It handles the initial setup of the
   * ERC-721 collection.
   *
   * This method also receives the bytes argument `initialArgs_`. This allows subsequent versions of this
   * contract to require new initialise arguments without the factory needing to change in order to supply
   * these arguments.
   *
   * @param name_ The name of this collection, typicaly a short string
   * @param symbol_ The symbol for this collection, commonly a short string in capital letters.
   * @param baseURI_ The base URI for this collection. URIs will be formed from this string, plus the URI suffix.
   * @param switches_ Array of collection control booleans:
   *        [0] uniqueMetadata: If the collection has unique metadata for each NFT. If this is true the baseURI
   *            is suffixed. If false the baseURI is returned for all.
   *        [1] transferable: Is this collection transferable? If false this collection is 'soulbound'
   *        [2] burnable: Is this collection burnable? If false this collection cannot be burned.
   * @param maxSupply_ The maximum number of tokens that can be minted in this collection. 0 = unlimited.
   * @param initialArgs_ A bytes parameter than can contain further, as of yet undefined, parameters.
   */
  function initialise(
    string calldata name_,
    string calldata symbol_,
    string calldata baseURI_,
    uint256 maxSupply_,
    bool[] calldata switches_,
    bytes calldata initialArgs_
  ) external;

  /**
   * -------------------
   * VIEW METHOD GETTERS
   * -------------------
   */

  /**
   * @dev getMerkleRoot
   *
   * External function to return the current merkle root
   */
  function getMerkleRoot() external view returns (bytes32);

  /**
   * @dev getAddressHasMinted
   *
   * External function to return if the address has minted on the queried collection.
   */
  function getAddressHasMinted(
    address minter_,
    uint256 tokenTypeId_
  ) external returns (bool);

  /**
   * @dev getAllTokenTypes
   *
   * External function to return full array of token types
   */
  function getAllTokenTypes() external view returns (uint16[] memory);

  /**
   * -------
   * UPDATES
   * -------
   */

  /**
   * @dev updateMerkleRoot
   *
   * An onlyAdmin method that allows the owner to update the root for the merkle tree.
   *
   * @param newMerkleRoot_ The new bytes32 merkle root.
   */
  function updateMerkleRoot(bytes32 newMerkleRoot_) external;

  /**
   * @dev updateUniqueMetadata
   *
   * onlyOwner method to update a the unique metadata boolean
   *
   * @param uniqueMetadata_: whether this collection has unique metadata (or not)
   */
  function updateUniqueMetadata(bool uniqueMetadata_) external;

  /**
   * @dev updateBaseURI
   *
   * onlyOwner method to update an unlocked URI
   *
   * @param uri_: the new URI
   */
  function updateBaseURI(string calldata uri_) external;

  /**
   * @dev updateURISuffixes
   *
   * onlyOwner method to update an unlocked URI suffix(es)
   *
   * @param uriSuffixes_: the new URI suffixes
   */
  function updateURISuffixes(URISuffixes[] calldata uriSuffixes_) external;

  /**
   * @dev lockURI
   *
   * onlyOwner method to lock the URI
   *
   * @param confirm_ confirmation value to prevent erroneous locking
   */
  function lockURI(bytes14 confirm_) external;

  /**
   * @dev lockMinting
   *
   * onlyOwner method to lock minting forever
   *
   * @param confirm_ confirmation value to prevent erroneous locking
   */
  function lockMinting(bytes14 confirm_) external;

  /**
   * ----------------
   * TOKEN OPERATIONS
   * ----------------
   */

  /**
   * @dev communityMint
   *
   * Validate caller eligiblity and mint
   *
   * @param mintRequests_ An array of mint requests. These include the
   * token type identifier and the corresponding proof.
   */
  function communityMint(MintRequest[] calldata mintRequests_) external;

  /**
   * @dev addressIsInMerkleTree
   *
   * An public method to check if an address and proof pass the merkle check. It returns this as a bool.
   *
   * @param proof_ The provided proof
   * @param leafHash_ The leaf hash being checked.
   */
  function addressIsInMerkleTree(
    bytes32[] calldata proof_,
    bytes32 leafHash_
  ) external view returns (bool);

  /**
   * @dev fixedMint
   *
   * Owner only fixed amount mint function
   *
   * @param mintRequests_ An array of mint requests.
   */
  function fixedMint(MintRequest[] calldata mintRequests_) external;

  /**
   * @dev burn
   *
   * Burn tokens!
   *
   * @param tokenId_: the tokenId to burn (note - owner / auth checks performed in ERC721Sub)
   */
  function burn(uint256 tokenId_) external;
}

File 6 of 7 : ERC721CC.sol
// SPDX-License-Identifier: MIT

/**
 * @title ERC721CC.sol. ERC721A implementation plus:
 *        - Clonable (minimal non-upgradeable proxies)
 *        - Indexing of tokenType to tokenId
 *        - Some other controls (e.g. transferable etc.)
 *
 * ---> Full credit to Chiru Labs for ERC721A Contracts v4.2.3 <---
 *
 * @author omnus (https://omn.us) for bywassies (https://bywassies.com)
 */

pragma solidity 0.8.24;

import {IERC721CC} from "./IERC721CC.sol";

/**
 * @dev Interface of ERC721 token receiver.
 */
interface ERC721A__IERC721Receiver {
  function onERC721Received(
    address operator,
    address from,
    uint256 tokenId,
    bytes calldata data
  ) external returns (bytes4);
}

/**
 * @title ERC721A
 *
 * @dev Implementation of the [ERC721](https://eips.ethereum.org/EIPS/eip-721)
 * Non-Fungible Token Standard, including the Metadata extension.
 * Optimized for lower gas during batch mints.
 *
 * Token IDs are minted in sequential order (e.g. 0, 1, 2, 3, ...)
 * starting from `_startTokenId()`.
 *
 * Assumptions:
 *
 * - An owner cannot have more than 2**64 - 1 (max value of uint64) of supply.
 * - The maximum token ID cannot exceed 2**40 - 1 (max value of uint48).
 */
contract ERC721CC is IERC721CC {
  // Bypass for a `--via-ir` bug (https://github.com/chiru-labs/ERC721A/pull/364).
  struct TokenApprovalRef {
    address value;
  }

  // =============================================================
  //                           CONSTANTS
  // =============================================================

  // Mask of an entry in packed address data.
  uint256 private constant _BITMASK_ADDRESS_DATA_ENTRY = (1 << 64) - 1;

  // The bit position of `numberMinted` in packed address data.
  uint256 private constant _BITPOS_NUMBER_MINTED = 64;

  // The bit position of `numberBurned` in packed address data.
  uint256 private constant _BITPOS_NUMBER_BURNED = 128;

  // The bit position of `aux` in packed address data.
  uint256 private constant _BITPOS_AUX = 192;

  // Mask of all 256 bits in packed address data except the 64 bits for `aux`.
  uint256 private constant _BITMASK_AUX_COMPLEMENT = (1 << 192) - 1;

  // The bit position of `startTimestamp` in packed ownership.
  uint256 private constant _BITPOS_START_TIMESTAMP = 160;

  // The bit mask of the `burned` bit in packed ownership.
  uint256 private constant _BITMASK_BURNED = 1 << 224;

  // The bit position of the `nextInitialized` bit in packed ownership.
  uint256 private constant _BITPOS_NEXT_INITIALIZED = 225;

  // The bit mask of the `nextInitialized` bit in packed ownership.
  uint256 private constant _BITMASK_NEXT_INITIALIZED = 1 << 225;

  // The bit position of `extraData` in packed ownership.
  uint256 private constant _BITPOS_EXTRA_DATA = 232;

  // Mask of all 256 bits in a packed ownership except the 24 bits for `extraData`.
  uint256 private constant _BITMASK_EXTRA_DATA_COMPLEMENT = (1 << 232) - 1;

  // The mask of the lower 160 bits for addresses.
  uint256 private constant _BITMASK_ADDRESS = (1 << 160) - 1;

  // The maximum `quantity` that can be minted with {_mintERC2309}.
  // This limit is to prevent overflows on the address data entries.
  // For a limit of 5000, a total of 3.689e15 calls to {_mintERC2309}
  // is required to cause an overflow, which is unrealistic.
  uint256 private constant _MAX_MINT_ERC2309_QUANTITY_LIMIT = 5000;

  // The `Transfer` event signature is given by:
  // `keccak256(bytes("Transfer(address,address,uint256)"))`.
  bytes32 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;

  // =============================================================
  //                            STORAGE
  // =============================================================

  // Token name
  string private _name;

  // Token symbol
  string private _symbol;

  // Base URI
  string public baseURI;

  // The next token ID to be minted.
  uint104 private _currentIndex;

  // The max supply
  uint104 public maxSupply;

  // Bool to lock modifications to the URI:
  bool public lockedURI;

  // Bool to lock all minting:
  bool public lockedMinting;

  // Does this collection have unique metadata for each token Id?
  bool public uniqueMetadata;

  // This bool is used in initialisation control. See also 'initialisationControl()'.
  bool public initialised;

  // Is this collection transferrable?
  bool public transferable;

  // Is this collection burnable?
  bool public burnable;

  // The number of tokens burned.
  uint256 private _burnCounter;

  // Mapping from token ID to ownership details
  // An empty struct value does not necessarily mean the token is unowned.
  // See {_packedOwnershipOf} implementation for details.
  //
  // Bits Layout:
  // - [0..159]   `addr`
  // - [160..223] `startTimestamp`
  // - [224]      `burned`
  // - [225]      `nextInitialized`
  // - [232..255] `extraData`
  mapping(uint256 => uint256) private _packedOwnerships;

  // Mapping owner address to address data.
  //
  // Bits Layout:
  // - [0..63]    `balance`
  // - [64..127]  `numberMinted`
  // - [128..191] `numberBurned`
  // - [192..255] `aux`
  mapping(address => uint256) private _packedAddressData;

  // Mapping from token ID to approved address.
  mapping(uint256 => TokenApprovalRef) private _tokenApprovals;

  // Mapping from owner to operator approvals
  mapping(address => mapping(address => bool)) private _operatorApprovals;

  // Array of tokeIds to tokenTypeId. Held as an array so multiple sequential writes
  // are in the same slot. Note this means the collection can have a maximum of 65,535 token types.
  uint16[] public tokenIdToTypeId;

  // Map the tokenIdType to the URI suffix:
  mapping(uint256 => string) uriSuffix;

  /**
   * @dev _initialiseERC721CC
   *
   * This method is called by the factory when creating a clone. It handles the initial setup of the
   * ERC-721 collection.
   *
   * This method also receives the bytes argument `initialArgs_`. This allows subsequent versions of this
   * contract to require new initialise arguments without the factory needing to change in order to supply
   * these arguments.
   *
   * @param name_ The name of this collection, typical a short string
   * @param symbol_ The symbol for this collection, commonly a short string in capital letters.
   * @param baseURI_ The base URI for this collection. URIs will be formed from this string, plus the tokenId
   * and then the string ".json"
   * @param switches_ Array of collection control booleans:
   *        [0] uniqueMetadata: If the collection has unique metadata for each NFT. If this is true the baseURI
   *            is suffixed with the tokenId + ".json". If false the baseURI is returned for all.
   *        [1] transferable: Is this collection transferable? If false this collection is 'soulbound'
   *        [2] burnable: Is this collection burnable? If false this collection cannot be burned.
   * @param maxSupply_ The maximum number of tokens that can be minted in this collection.
   */
  function _initialiseERC721CC(
    string calldata name_,
    string calldata symbol_,
    string calldata baseURI_,
    uint256 maxSupply_,
    bool[] calldata switches_,
    // Initialisation args allows us to pass currently uknown config data to the
    // template without needing to upgrade the factory. Currently unused in this version.
    bytes calldata
  ) internal {
    _initialisationControl();
    _name = name_;
    _symbol = symbol_;
    baseURI = baseURI_;
    if (maxSupply_ > type(uint104).max) {
      revert("Max supply exceeds uint104");
    }
    maxSupply = uint104(maxSupply_);
    _currentIndex = uint104(_startTokenId());
    uniqueMetadata = switches_[0];
    transferable = switches_[1];
    burnable = switches_[2];
    // Push a blank tokenIdToTokenTypeId so we line up with an 1 indexed collection
    tokenIdToTypeId.push(0);
  }

  /**
   * @dev _initialisationControl
   *
   * An internal method that checks that the `initialise` bool is not true. It will revert if this
   * bool is true. It them sets `initialise` to true to ensure that it can only be called a single time.
   */
  function _initialisationControl() internal {
    if (initialised) {
      revert("Can only intialise once");
    }
    initialised = true;
  }

  // =============================================================
  //                   TOKEN COUNTING OPERATIONS
  // =============================================================

  /**
   * @dev Returns the starting token ID.
   * To change the starting token ID, please override this function.
   */
  function _startTokenId() internal view virtual returns (uint256) {
    return 1;
  }

  /**
   * @dev Returns the next token ID to be minted.
   */
  function _nextTokenId() internal view virtual returns (uint256) {
    return _currentIndex;
  }

  /**
   * @dev Returns the total number of tokens in existence.
   * Burned tokens will reduce the count.
   * To get the total number of tokens minted, please see {_totalMinted}.
   */
  function totalSupply() public view virtual override returns (uint256) {
    // Counter underflow is impossible as _burnCounter cannot be incremented
    // more than `_currentIndex - _startTokenId()` times.
    unchecked {
      return _currentIndex - _burnCounter - _startTokenId();
    }
  }

  /**
   * @dev Returns the total amount of tokens minted in the contract.
   */
  function _totalMinted() internal view virtual returns (uint256) {
    // Counter underflow is impossible as `_currentIndex` does not decrement,
    // and it is initialized to `_startTokenId()`.
    unchecked {
      return _currentIndex - _startTokenId();
    }
  }

  /**
   * @dev Returns the total number of tokens burned.
   */
  function _totalBurned() internal view virtual returns (uint256) {
    return _burnCounter;
  }

  // =============================================================
  //                    ADDRESS DATA OPERATIONS
  // =============================================================

  /**
   * @dev Returns the number of tokens in `owner`'s account.
   */
  function balanceOf(
    address owner
  ) public view virtual override returns (uint256) {
    if (owner == address(0)) _revert(BalanceQueryForZeroAddress.selector);
    return _packedAddressData[owner] & _BITMASK_ADDRESS_DATA_ENTRY;
  }

  /**
   * Returns the number of tokens minted by `owner`.
   */
  function _numberMinted(address owner) internal view returns (uint256) {
    return
      (_packedAddressData[owner] >> _BITPOS_NUMBER_MINTED) &
      _BITMASK_ADDRESS_DATA_ENTRY;
  }

  /**
   * Returns the number of tokens burned by or on behalf of `owner`.
   */
  function _numberBurned(address owner) internal view returns (uint256) {
    return
      (_packedAddressData[owner] >> _BITPOS_NUMBER_BURNED) &
      _BITMASK_ADDRESS_DATA_ENTRY;
  }

  /**
   * Returns the auxiliary data for `owner`. (e.g. number of whitelist mint slots used).
   */
  function _getAux(address owner) internal view returns (uint64) {
    return uint64(_packedAddressData[owner] >> _BITPOS_AUX);
  }

  /**
   * Sets the auxiliary data for `owner`. (e.g. number of whitelist mint slots used).
   * If there are multiple variables, please pack them into a uint64.
   */
  function _setAux(address owner, uint64 aux) internal virtual {
    uint256 packed = _packedAddressData[owner];
    uint256 auxCasted;
    // Cast `aux` with assembly to avoid redundant masking.
    assembly {
      auxCasted := aux
    }
    packed = (packed & _BITMASK_AUX_COMPLEMENT) | (auxCasted << _BITPOS_AUX);
    _packedAddressData[owner] = packed;
  }

  // =============================================================
  //                            IERC165
  // =============================================================

  /**
   * @dev Returns true if this contract implements the interface defined by
   * `interfaceId`. See the corresponding
   * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
   * to learn more about how these ids are created.
   *
   * This function call must use less than 30000 gas.
   */
  function supportsInterface(
    bytes4 interfaceId
  ) public view virtual override returns (bool) {
    // The interface IDs are constants representing the first 4 bytes
    // of the XOR of all function selectors in the interface.
    // See: [ERC165](https://eips.ethereum.org/EIPS/eip-165)
    // (e.g. `bytes4(i.functionA.selector ^ i.functionB.selector ^ ...)`)
    return
      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
      interfaceId == 0x80ac58cd || // ERC165 interface ID for ERC721.
      interfaceId == 0x5b5e139f; // ERC165 interface ID for ERC721Metadata.
  }

  // =============================================================
  //                        IERC721Metadata
  // =============================================================

  /**
   * @dev Returns the token collection name.
   */
  function name() public view virtual override returns (string memory) {
    return _name;
  }

  /**
   * @dev Returns the token collection symbol.
   */
  function symbol() public view virtual override returns (string memory) {
    return _symbol;
  }

  /**
   * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
   */
  function tokenURI(
    uint256 tokenId
  ) public view virtual override returns (string memory) {
    if (!_exists(tokenId)) _revert(URIQueryForNonexistentToken.selector);

    if (uniqueMetadata) {
      return
        bytes(baseURI).length != 0
          ? string(
            abi.encodePacked(baseURI, uriSuffix[tokenIdToTypeId[tokenId]])
          )
          : "";
    } else {
      return baseURI;
    }
  }

  // =============================================================
  //                     OWNERSHIPS OPERATIONS
  // =============================================================

  /**
   * @dev Returns the owner of the `tokenId` token.
   *
   * Requirements:
   *
   * - `tokenId` must exist.
   */
  function ownerOf(
    uint256 tokenId
  ) public view virtual override returns (address) {
    return address(uint160(_packedOwnershipOf(tokenId)));
  }

  /**
   * @dev Gas spent here starts off proportional to the maximum mint batch size.
   * It gradually moves to O(1) as tokens get transferred around over time.
   */
  function _ownershipOf(
    uint256 tokenId
  ) internal view virtual returns (TokenOwnership memory) {
    return _unpackedOwnership(_packedOwnershipOf(tokenId));
  }

  /**
   * @dev Returns the unpacked `TokenOwnership` struct at `index`.
   */
  function _ownershipAt(
    uint256 index
  ) internal view virtual returns (TokenOwnership memory) {
    return _unpackedOwnership(_packedOwnerships[index]);
  }

  /**
   * @dev Returns whether the ownership slot at `index` is initialized.
   * An uninitialized slot does not necessarily mean that the slot has no owner.
   */
  function _ownershipIsInitialized(
    uint256 index
  ) internal view virtual returns (bool) {
    return _packedOwnerships[index] != 0;
  }

  /**
   * @dev Initializes the ownership slot minted at `index` for efficiency purposes.
   */
  function _initializeOwnershipAt(uint256 index) internal virtual {
    if (_packedOwnerships[index] == 0) {
      _packedOwnerships[index] = _packedOwnershipOf(index);
    }
  }

  /**
   * Returns the packed ownership data of `tokenId`.
   */
  function _packedOwnershipOf(
    uint256 tokenId
  ) private view returns (uint256 packed) {
    if (_startTokenId() <= tokenId) {
      packed = _packedOwnerships[tokenId];
      // If the data at the starting slot does not exist, start the scan.
      if (packed == 0) {
        if (tokenId >= _currentIndex)
          _revert(OwnerQueryForNonexistentToken.selector);
        // Invariant:
        // There will always be an initialized ownership slot
        // (i.e. `ownership.addr != address(0) && ownership.burned == false`)
        // before an unintialized ownership slot
        // (i.e. `ownership.addr == address(0) && ownership.burned == false`)
        // Hence, `tokenId` will not underflow.
        //
        // We can directly compare the packed value.
        // If the address is zero, packed will be zero.
        for (;;) {
          unchecked {
            packed = _packedOwnerships[--tokenId];
          }
          if (packed == 0) continue;
          if (packed & _BITMASK_BURNED == 0) return packed;
          // Otherwise, the token is burned, and we must revert.
          // This handles the case of batch burned tokens, where only the burned bit
          // of the starting slot is set, and remaining slots are left uninitialized.
          _revert(OwnerQueryForNonexistentToken.selector);
        }
      }
      // Otherwise, the data exists and we can skip the scan.
      // This is possible because we have already achieved the target condition.
      // This saves 2143 gas on transfers of initialized tokens.
      // If the token is not burned, return `packed`. Otherwise, revert.
      if (packed & _BITMASK_BURNED == 0) return packed;
    }
    _revert(OwnerQueryForNonexistentToken.selector);
  }

  /**
   * @dev Returns the unpacked `TokenOwnership` struct from `packed`.
   */
  function _unpackedOwnership(
    uint256 packed
  ) private pure returns (TokenOwnership memory ownership) {
    ownership.addr = address(uint160(packed));
    ownership.startTimestamp = uint64(packed >> _BITPOS_START_TIMESTAMP);
    ownership.burned = packed & _BITMASK_BURNED != 0;
    ownership.extraData = uint24(packed >> _BITPOS_EXTRA_DATA);
  }

  /**
   * @dev Packs ownership data into a single uint256.
   */
  function _packOwnershipData(
    address owner,
    uint256 flags
  ) private view returns (uint256 result) {
    assembly {
      // Mask `owner` to the lower 160 bits, in case the upper bits somehow aren't clean.
      owner := and(owner, _BITMASK_ADDRESS)
      // `owner | (block.timestamp << _BITPOS_START_TIMESTAMP) | flags`.
      result := or(owner, or(shl(_BITPOS_START_TIMESTAMP, timestamp()), flags))
    }
  }

  /**
   * @dev Returns the `nextInitialized` flag set if `quantity` equals 1.
   */
  function _nextInitializedFlag(
    uint256 quantity
  ) private pure returns (uint256 result) {
    // For branchless setting of the `nextInitialized` flag.
    assembly {
      // `(quantity == 1) << _BITPOS_NEXT_INITIALIZED`.
      result := shl(_BITPOS_NEXT_INITIALIZED, eq(quantity, 1))
    }
  }

  // =============================================================
  //                      APPROVAL OPERATIONS
  // =============================================================

  /**
   * @dev Gives permission to `to` to transfer `tokenId` token to another account. See {ERC721A-_approve}.
   *
   * Requirements:
   *
   * - The caller must own the token or be an approved operator.
   */
  function approve(
    address to,
    uint256 tokenId
  ) public payable virtual override {
    _approve(to, tokenId, true);
  }

  /**
   * @dev Returns the account approved for `tokenId` token.
   *
   * Requirements:
   *
   * - `tokenId` must exist.
   */
  function getApproved(
    uint256 tokenId
  ) public view virtual override returns (address) {
    if (!_exists(tokenId)) _revert(ApprovalQueryForNonexistentToken.selector);

    return _tokenApprovals[tokenId].value;
  }

  /**
   * @dev Approve or remove `operator` as an operator for the caller.
   * Operators can call {transferFrom} or {safeTransferFrom}
   * for any token owned by the caller.
   *
   * Requirements:
   *
   * - The `operator` cannot be the caller.
   *
   * Emits an {ApprovalForAll} event.
   */
  function setApprovalForAll(
    address operator,
    bool approved
  ) public virtual override {
    _operatorApprovals[_msgSenderERC721A()][operator] = approved;
    emit ApprovalForAll(_msgSenderERC721A(), operator, approved);
  }

  /**
   * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
   *
   * See {setApprovalForAll}.
   */
  function isApprovedForAll(
    address owner,
    address operator
  ) public view virtual override returns (bool) {
    return _operatorApprovals[owner][operator];
  }

  /**
   * @dev Returns whether `tokenId` exists.
   *
   * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
   *
   * Tokens start existing when they are minted. See {_mint}.
   */
  function _exists(
    uint256 tokenId
  ) internal view virtual returns (bool result) {
    if (_startTokenId() <= tokenId) {
      if (tokenId < _currentIndex) {
        uint256 packed;
        while ((packed = _packedOwnerships[tokenId]) == 0) --tokenId;
        result = packed & _BITMASK_BURNED == 0;
      }
    }
  }

  /**
   * @dev Returns whether `msgSender` is equal to `approvedAddress` or `owner`.
   */
  function _isSenderApprovedOrOwner(
    address approvedAddress,
    address owner,
    address msgSender
  ) private pure returns (bool result) {
    assembly {
      // Mask `owner` to the lower 160 bits, in case the upper bits somehow aren't clean.
      owner := and(owner, _BITMASK_ADDRESS)
      // Mask `msgSender` to the lower 160 bits, in case the upper bits somehow aren't clean.
      msgSender := and(msgSender, _BITMASK_ADDRESS)
      // `msgSender == owner || msgSender == approvedAddress`.
      result := or(eq(msgSender, owner), eq(msgSender, approvedAddress))
    }
  }

  /**
   * @dev Returns the storage slot and value for the approved address of `tokenId`.
   */
  function _getApprovedSlotAndAddress(
    uint256 tokenId
  )
    private
    view
    returns (uint256 approvedAddressSlot, address approvedAddress)
  {
    TokenApprovalRef storage tokenApproval = _tokenApprovals[tokenId];
    // The following is equivalent to `approvedAddress = _tokenApprovals[tokenId].value`.
    assembly {
      approvedAddressSlot := tokenApproval.slot
      approvedAddress := sload(approvedAddressSlot)
    }
  }

  // =============================================================
  //                      TRANSFER OPERATIONS
  // =============================================================

  /**
   * @dev Transfers `tokenId` from `from` to `to`.
   *
   * Requirements:
   *
   * - `from` cannot be the zero address.
   * - `to` cannot be the zero address.
   * - `tokenId` token must be owned by `from`.
   * - If the caller is not `from`, it must be approved to move this token
   * by either {approve} or {setApprovalForAll}.
   *
   * Emits a {Transfer} event.
   */
  function transferFrom(
    address from,
    address to,
    uint256 tokenId
  ) public payable virtual override {
    // Cannot transfer soulbound or soulchained collections:
    if (!transferable) {
      revert("Collection not transferable");
    }

    uint256 prevOwnershipPacked = _packedOwnershipOf(tokenId);

    // Mask `from` to the lower 160 bits, in case the upper bits somehow aren't clean.
    from = address(uint160(uint256(uint160(from)) & _BITMASK_ADDRESS));

    if (address(uint160(prevOwnershipPacked)) != from)
      _revert(TransferFromIncorrectOwner.selector);

    (
      uint256 approvedAddressSlot,
      address approvedAddress
    ) = _getApprovedSlotAndAddress(tokenId);

    // The nested ifs save around 20+ gas over a compound boolean condition.
    if (!_isSenderApprovedOrOwner(approvedAddress, from, _msgSenderERC721A()))
      if (!isApprovedForAll(from, _msgSenderERC721A()))
        _revert(TransferCallerNotOwnerNorApproved.selector);

    _beforeTokenTransfers(from, to, tokenId, 1);

    // Clear approvals from the previous owner.
    assembly {
      if approvedAddress {
        // This is equivalent to `delete _tokenApprovals[tokenId]`.
        sstore(approvedAddressSlot, 0)
      }
    }

    // Underflow of the sender's balance is impossible because we check for
    // ownership above and the recipient's balance can't realistically overflow.
    // Counter overflow is incredibly unrealistic as `tokenId` would have to be 2**256.
    unchecked {
      // We can directly increment and decrement the balances.
      --_packedAddressData[from]; // Updates: `balance -= 1`.
      ++_packedAddressData[to]; // Updates: `balance += 1`.

      // Updates:
      // - `address` to the next owner.
      // - `startTimestamp` to the timestamp of transfering.
      // - `burned` to `false`.
      // - `nextInitialized` to `true`.
      _packedOwnerships[tokenId] = _packOwnershipData(
        to,
        _BITMASK_NEXT_INITIALIZED |
          _nextExtraData(from, to, prevOwnershipPacked)
      );

      // If the next slot may not have been initialized (i.e. `nextInitialized == false`) .
      if (prevOwnershipPacked & _BITMASK_NEXT_INITIALIZED == 0) {
        uint256 nextTokenId = tokenId + 1;
        // If the next slot's address is zero and not burned (i.e. packed value is zero).
        if (_packedOwnerships[nextTokenId] == 0) {
          // If the next slot is within bounds.
          if (nextTokenId != _currentIndex) {
            // Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
            _packedOwnerships[nextTokenId] = prevOwnershipPacked;
          }
        }
      }
    }

    // Mask `to` to the lower 160 bits, in case the upper bits somehow aren't clean.
    uint256 toMasked = uint256(uint160(to)) & _BITMASK_ADDRESS;
    assembly {
      // Emit the `Transfer` event.
      log4(
        0, // Start of data (0, since no data).
        0, // End of data (0, since no data).
        _TRANSFER_EVENT_SIGNATURE, // Signature.
        from, // `from`.
        toMasked, // `to`.
        tokenId // `tokenId`.
      )
    }
    if (toMasked == 0) _revert(TransferToZeroAddress.selector);

    _afterTokenTransfers(from, to, tokenId, 1);
  }

  /**
   * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
   */
  function safeTransferFrom(
    address from,
    address to,
    uint256 tokenId
  ) public payable virtual override {
    safeTransferFrom(from, to, tokenId, "");
  }

  /**
   * @dev Safely transfers `tokenId` token from `from` to `to`.
   *
   * Requirements:
   *
   * - `from` cannot be the zero address.
   * - `to` cannot be the zero address.
   * - `tokenId` token must exist and be owned by `from`.
   * - If the caller is not `from`, it must be approved to move this token
   * by either {approve} or {setApprovalForAll}.
   * - If `to` refers to a smart contract, it must implement
   * {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
   *
   * Emits a {Transfer} event.
   */
  function safeTransferFrom(
    address from,
    address to,
    uint256 tokenId,
    bytes memory _data
  ) public payable virtual override {
    transferFrom(from, to, tokenId);
    if (to.code.length != 0)
      if (!_checkContractOnERC721Received(from, to, tokenId, _data)) {
        _revert(TransferToNonERC721ReceiverImplementer.selector);
      }
  }

  /**
   * @dev Hook that is called before a set of serially-ordered token IDs
   * are about to be transferred. This includes minting.
   * And also called before burning one token.
   *
   * `startTokenId` - the first token ID to be transferred.
   * `quantity` - the amount to be transferred.
   *
   * Calling conditions:
   *
   * - When `from` and `to` are both non-zero, `from`'s `tokenId` will be
   * transferred to `to`.
   * - When `from` is zero, `tokenId` will be minted for `to`.
   * - When `to` is zero, `tokenId` will be burned by `from`.
   * - `from` and `to` are never both zero.
   */
  function _beforeTokenTransfers(
    address from,
    address to,
    uint256 startTokenId,
    uint256 quantity
  ) internal virtual {}

  /**
   * @dev Hook that is called after a set of serially-ordered token IDs
   * have been transferred. This includes minting.
   * And also called after one token has been burned.
   *
   * `startTokenId` - the first token ID to be transferred.
   * `quantity` - the amount to be transferred.
   *
   * Calling conditions:
   *
   * - When `from` and `to` are both non-zero, `from`'s `tokenId` has been
   * transferred to `to`.
   * - When `from` is zero, `tokenId` has been minted for `to`.
   * - When `to` is zero, `tokenId` has been burned by `from`.
   * - `from` and `to` are never both zero.
   */
  function _afterTokenTransfers(
    address from,
    address to,
    uint256 startTokenId,
    uint256 quantity
  ) internal virtual {}

  /**
   * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target contract.
   *
   * `from` - Previous owner of the given token ID.
   * `to` - Target address that will receive the token.
   * `tokenId` - Token ID to be transferred.
   * `_data` - Optional data to send along with the call.
   *
   * Returns whether the call correctly returned the expected magic value.
   */
  function _checkContractOnERC721Received(
    address from,
    address to,
    uint256 tokenId,
    bytes memory _data
  ) private returns (bool) {
    try
      ERC721A__IERC721Receiver(to).onERC721Received(
        _msgSenderERC721A(),
        from,
        tokenId,
        _data
      )
    returns (bytes4 retval) {
      return retval == ERC721A__IERC721Receiver(to).onERC721Received.selector;
    } catch (bytes memory reason) {
      if (reason.length == 0) {
        _revert(TransferToNonERC721ReceiverImplementer.selector);
      }
      assembly {
        revert(add(32, reason), mload(reason))
      }
    }
  }

  // =============================================================
  //                        MINT OPERATIONS
  // =============================================================

  /**
   * @dev Mints `quantity` tokens and transfers them to `to`.
   *
   * Requirements:
   *
   * - `to` cannot be the zero address.
   * - `quantity` must be greater than 0.
   *
   * Emits a {Transfer} event for each mint.
   */
  function _mint(
    address to,
    uint256 quantity,
    MintRequest[] calldata mintRequests
  ) internal virtual {
    _maxSupplyCheck(quantity);

    uint256 startTokenId = _currentIndex;
    if (quantity == 0) _revert(MintZeroQuantity.selector);

    _beforeTokenTransfers(address(0), to, startTokenId, quantity);

    // Overflows are incredibly unrealistic.
    // `balance` and `numberMinted` have a maximum limit of 2**64.
    // `tokenId` has a maximum limit of 2**256.
    unchecked {
      // Updates:
      // - `address` to the owner.
      // - `startTimestamp` to the timestamp of minting.
      // - `burned` to `false`.
      // - `nextInitialized` to `quantity == 1`.
      _packedOwnerships[startTokenId] = _packOwnershipData(
        to,
        _nextInitializedFlag(quantity) | _nextExtraData(address(0), to, 0)
      );

      // Updates:
      // - `balance += quantity`.
      // - `numberMinted += quantity`.
      //
      // We can directly add to the `balance` and `numberMinted`.
      _packedAddressData[to] += quantity * ((1 << _BITPOS_NUMBER_MINTED) | 1);

      // Mask `to` to the lower 160 bits, in case the upper bits somehow aren't clean.
      uint256 toMasked = uint256(uint160(to)) & _BITMASK_ADDRESS;

      if (toMasked == 0) _revert(MintToZeroAddress.selector);

      uint256 end = startTokenId + quantity;
      uint256 tokenId = startTokenId;

      uint256 i = 0;

      do {
        assembly {
          // Emit the `Transfer` event.
          log4(
            0, // Start of data (0, since no data).
            0, // End of data (0, since no data).
            _TRANSFER_EVENT_SIGNATURE, // Signature.
            0, // `address(0)`.
            toMasked, // `to`.
            tokenId // `tokenId`.
          )
        }

        // Record the tokenTypeId for this token:
        tokenIdToTypeId.push(uint16(mintRequests[i].tokenTypeId));

        i++;

        // The `!=` check ensures that large values of `quantity`
        // that overflows uint256 will make the loop run out of gas.
      } while (++tokenId != end);

      _currentIndex = uint104(end);
    }
    _afterTokenTransfers(address(0), to, startTokenId, quantity);
  }

  /**
   * @dev _maxSupplyCheck
   *
   * An internal method to check if the max supply has been reached. It will revert if it has.
   */
  function _maxSupplyCheck(uint256 quantity) internal view {
    // Note that a max supply of 0 means this collection has NO LIMIT on supply. The only limit
    // in this case will be from the allowlist mechanism used.
    if (maxSupply != 0) {
      uint256 newSupply = _totalMinted() + quantity;
      if (newSupply > maxSupply) {
        revert("Max supply reached");
      }
      if (newSupply > type(uint104).max) {
        revert("Collection size cannot exceed max uint104");
      }
    }
  }

  /**
   * @dev Mints `quantity` tokens and transfers them to `to`.
   *
   * This function is intended for efficient minting only during contract creation.
   *
   * It emits only one {ConsecutiveTransfer} as defined in
   * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309),
   * instead of a sequence of {Transfer} event(s).
   *
   * Calling this function outside of contract creation WILL make your contract
   * non-compliant with the ERC721 standard.
   * For full ERC721 compliance, substituting ERC721 {Transfer} event(s) with the ERC2309
   * {ConsecutiveTransfer} event is only permissible during contract creation.
   *
   * Requirements:
   *
   * - `to` cannot be the zero address.
   * - `quantity` must be greater than 0.
   *
   * Emits a {ConsecutiveTransfer} event.
   */
  function _mintERC2309(address to, uint256 quantity) internal virtual {
    uint256 startTokenId = _currentIndex;
    if (to == address(0)) _revert(MintToZeroAddress.selector);
    if (quantity == 0) _revert(MintZeroQuantity.selector);
    if (quantity > _MAX_MINT_ERC2309_QUANTITY_LIMIT)
      _revert(MintERC2309QuantityExceedsLimit.selector);

    _beforeTokenTransfers(address(0), to, startTokenId, quantity);

    // Overflows are unrealistic due to the above check for `quantity` to be below the limit.
    unchecked {
      // Updates:
      // - `balance += quantity`.
      // - `numberMinted += quantity`.
      //
      // We can directly add to the `balance` and `numberMinted`.
      _packedAddressData[to] += quantity * ((1 << _BITPOS_NUMBER_MINTED) | 1);

      // Updates:
      // - `address` to the owner.
      // - `startTimestamp` to the timestamp of minting.
      // - `burned` to `false`.
      // - `nextInitialized` to `quantity == 1`.
      _packedOwnerships[startTokenId] = _packOwnershipData(
        to,
        _nextInitializedFlag(quantity) | _nextExtraData(address(0), to, 0)
      );

      emit ConsecutiveTransfer(
        startTokenId,
        startTokenId + quantity - 1,
        address(0),
        to
      );

      _currentIndex = uint104(startTokenId + quantity);
    }
    _afterTokenTransfers(address(0), to, startTokenId, quantity);
  }

  // =============================================================
  //                       APPROVAL OPERATIONS
  // =============================================================

  /**
   * @dev Equivalent to `_approve(to, tokenId, false)`.
   */
  function _approve(address to, uint256 tokenId) internal virtual {
    _approve(to, tokenId, false);
  }

  /**
   * @dev Gives permission to `to` to transfer `tokenId` token to another account.
   * The approval is cleared when the token is transferred.
   *
   * Only a single account can be approved at a time, so approving the
   * zero address clears previous approvals.
   *
   * Requirements:
   *
   * - `tokenId` must exist.
   *
   * Emits an {Approval} event.
   */
  function _approve(
    address to,
    uint256 tokenId,
    bool approvalCheck
  ) internal virtual {
    address owner = ownerOf(tokenId);

    if (approvalCheck && _msgSenderERC721A() != owner)
      if (!isApprovedForAll(owner, _msgSenderERC721A())) {
        _revert(ApprovalCallerNotOwnerNorApproved.selector);
      }

    _tokenApprovals[tokenId].value = to;
    emit Approval(owner, to, tokenId);
  }

  // =============================================================
  //                        BURN OPERATIONS
  // =============================================================

  /**
   * @dev Equivalent to `_burn(tokenId, false)`.
   */
  function _burn(uint256 tokenId) internal virtual {
    _burn(tokenId, false);
  }

  /**
   * @dev Destroys `tokenId`.
   * The approval is cleared when the token is burned.
   *
   * Requirements:
   *
   * - `tokenId` must exist.
   *
   * Emits a {Transfer} event.
   */
  function _burn(uint256 tokenId, bool approvalCheck) internal virtual {
    // Cannot burn immortal or soulchained collections:
    if (!burnable) {
      revert("Collection not burnable");
    }

    uint256 prevOwnershipPacked = _packedOwnershipOf(tokenId);

    address from = address(uint160(prevOwnershipPacked));

    (
      uint256 approvedAddressSlot,
      address approvedAddress
    ) = _getApprovedSlotAndAddress(tokenId);

    if (approvalCheck) {
      // The nested ifs save around 20+ gas over a compound boolean condition.
      if (!_isSenderApprovedOrOwner(approvedAddress, from, _msgSenderERC721A()))
        if (!isApprovedForAll(from, _msgSenderERC721A()))
          _revert(TransferCallerNotOwnerNorApproved.selector);
    }

    _beforeTokenTransfers(from, address(0), tokenId, 1);

    // Clear approvals from the previous owner.
    assembly {
      if approvedAddress {
        // This is equivalent to `delete _tokenApprovals[tokenId]`.
        sstore(approvedAddressSlot, 0)
      }
    }

    // Underflow of the sender's balance is impossible because we check for
    // ownership above and the recipient's balance can't realistically overflow.
    // Counter overflow is incredibly unrealistic as `tokenId` would have to be 2**256.
    unchecked {
      // Updates:
      // - `balance -= 1`.
      // - `numberBurned += 1`.
      //
      // We can directly decrement the balance, and increment the number burned.
      // This is equivalent to `packed -= 1; packed += 1 << _BITPOS_NUMBER_BURNED;`.
      _packedAddressData[from] += (1 << _BITPOS_NUMBER_BURNED) - 1;

      // Updates:
      // - `address` to the last owner.
      // - `startTimestamp` to the timestamp of burning.
      // - `burned` to `true`.
      // - `nextInitialized` to `true`.
      _packedOwnerships[tokenId] = _packOwnershipData(
        from,
        (_BITMASK_BURNED | _BITMASK_NEXT_INITIALIZED) |
          _nextExtraData(from, address(0), prevOwnershipPacked)
      );

      // If the next slot may not have been initialized (i.e. `nextInitialized == false`) .
      if (prevOwnershipPacked & _BITMASK_NEXT_INITIALIZED == 0) {
        uint256 nextTokenId = tokenId + 1;
        // If the next slot's address is zero and not burned (i.e. packed value is zero).
        if (_packedOwnerships[nextTokenId] == 0) {
          // If the next slot is within bounds.
          if (nextTokenId != _currentIndex) {
            // Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
            _packedOwnerships[nextTokenId] = prevOwnershipPacked;
          }
        }
      }
    }

    emit Transfer(from, address(0), tokenId);
    _afterTokenTransfers(from, address(0), tokenId, 1);

    // Overflow not possible, as _burnCounter cannot be exceed _currentIndex times.
    unchecked {
      _burnCounter++;
    }
  }

  // =============================================================
  //                     EXTRA DATA OPERATIONS
  // =============================================================

  /**
   * @dev Directly sets the extra data for the ownership data `index`.
   */
  function _setExtraDataAt(uint256 index, uint24 extraData) internal virtual {
    uint256 packed = _packedOwnerships[index];
    if (packed == 0) _revert(OwnershipNotInitializedForExtraData.selector);
    uint256 extraDataCasted;
    // Cast `extraData` with assembly to avoid redundant masking.
    assembly {
      extraDataCasted := extraData
    }
    packed =
      (packed & _BITMASK_EXTRA_DATA_COMPLEMENT) |
      (extraDataCasted << _BITPOS_EXTRA_DATA);
    _packedOwnerships[index] = packed;
  }

  /**
   * @dev Called during each token transfer to set the 24bit `extraData` field.
   * Intended to be overridden by the cosumer contract.
   *
   * `previousExtraData` - the value of `extraData` before transfer.
   *
   * Calling conditions:
   *
   * - When `from` and `to` are both non-zero, `from`'s `tokenId` will be
   * transferred to `to`.
   * - When `from` is zero, `tokenId` will be minted for `to`.
   * - When `to` is zero, `tokenId` will be burned by `from`.
   * - `from` and `to` are never both zero.
   */
  function _extraData(
    address from,
    address to,
    uint24 previousExtraData
  ) internal view virtual returns (uint24) {}

  /**
   * @dev Returns the next extra data for the packed ownership data.
   * The returned result is shifted into position.
   */
  function _nextExtraData(
    address from,
    address to,
    uint256 prevOwnershipPacked
  ) private view returns (uint256) {
    uint24 extraData = uint24(prevOwnershipPacked >> _BITPOS_EXTRA_DATA);
    return uint256(_extraData(from, to, extraData)) << _BITPOS_EXTRA_DATA;
  }

  // =============================================================
  //                       OTHER OPERATIONS
  // =============================================================

  /**
   * @dev Returns the message sender (defaults to `msg.sender`).
   *
   * If you are writing GSN compatible contracts, you need to override this function.
   */
  function _msgSenderERC721A() internal view virtual returns (address) {
    return msg.sender;
  }

  /**
   * @dev Converts a uint256 to its ASCII string decimal representation.
   */
  function _toString(
    uint256 value
  ) internal pure virtual returns (string memory str) {
    assembly {
      // The maximum value of a uint256 contains 78 digits (1 byte per digit), but
      // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
      // We will need 1 word for the trailing zeros padding, 1 word for the length,
      // and 3 words for a maximum of 78 digits. Total: 5 * 0x20 = 0xa0.
      let m := add(mload(0x40), 0xa0)
      // Update the free memory pointer to allocate.
      mstore(0x40, m)
      // Assign the `str` to the end.
      str := sub(m, 0x20)
      // Zeroize the slot after the string.
      mstore(str, 0)

      // Cache the end of the memory to calculate the length later.
      let end := str

      // We write the string from rightmost digit to leftmost digit.
      // The following is essentially a do-while loop that also handles the zero case.
      // prettier-ignore
      for { let temp := value } 1 {} {
                str := sub(str, 1)
                // Write the character to the pointer.
                // The ASCII index of the '0' character is 48.
                mstore8(str, add(48, mod(temp, 10)))
                // Keep dividing `temp` until zero.
                temp := div(temp, 10)
                // prettier-ignore
                if iszero(temp) { break }
            }

      let length := sub(end, str)
      // Move the pointer 32 bytes leftwards to make room for the length.
      str := sub(str, 0x20)
      // Store the length.
      mstore(str, length)
    }
  }

  /**
   * @dev For more efficient reverts.
   */
  function _revert(bytes4 errorSelector) internal pure {
    assembly {
      mstore(0x00, errorSelector)
      revert(0x00, 0x04)
    }
  }
}

File 7 of 7 : IERC721CC.sol
// SPDX-License-Identifier: MIT

/**
 * @title IERC721CC.sol. IERC721A implementation plus:
 *        - Clonable (minimal non-upgradeable proxies)
 *        - Some other controls (e.g. transferable etc.)
 *
 * ---> Full credit to Chiru Labs for ERC721A Contracts v4.2.3 <---
 *
 * @author omnus (https://omn.us) for bywassies (https://bywassies.com)
 */

pragma solidity 0.8.24;

/**
 * @dev Interface of ERC721A.
 */
interface IERC721CC {
  struct MintRequest {
    uint256 tokenTypeId;
    bytes32[] proof;
  }

  /**
   * The caller must own the token or be an approved operator.
   */
  error ApprovalCallerNotOwnerNorApproved();

  /**
   * The token does not exist.
   */
  error ApprovalQueryForNonexistentToken();

  /**
   * Cannot query the balance for the zero address.
   */
  error BalanceQueryForZeroAddress();

  /**
   * Cannot mint to the zero address.
   */
  error MintToZeroAddress();

  /**
   * The quantity of tokens minted must be more than zero.
   */
  error MintZeroQuantity();

  /**
   * The token does not exist.
   */
  error OwnerQueryForNonexistentToken();

  /**
   * The caller must own the token or be an approved operator.
   */
  error TransferCallerNotOwnerNorApproved();

  /**
   * The token must be owned by `from`.
   */
  error TransferFromIncorrectOwner();

  /**
   * Cannot safely transfer to a contract that does not implement the
   * ERC721Receiver interface.
   */
  error TransferToNonERC721ReceiverImplementer();

  /**
   * Cannot transfer to the zero address.
   */
  error TransferToZeroAddress();

  /**
   * The token does not exist.
   */
  error URIQueryForNonexistentToken();

  /**
   * The `quantity` minted with ERC2309 exceeds the safety limit.
   */
  error MintERC2309QuantityExceedsLimit();

  /**
   * The `extraData` cannot be set on an unintialized ownership slot.
   */
  error OwnershipNotInitializedForExtraData();

  // =============================================================
  //                             ENUMS
  // =============================================================

  enum EternalContext {
    //             Transfer | Burn
    //             ===============
    Mortal, //        Yes   |  Yes     Tokens can be transferred and burned
    Immortal, //      Yes   |  No      Tokens can be transferred but NOT burned
    Soulbound, //     No    |  Yes     Tokens cannot be transferred but CAN be burned
    Soulchained //    No    |  No      Tokens cannot be transferred OR burned
  }

  // =============================================================
  //                            STRUCTS
  // =============================================================

  struct TokenOwnership {
    // The address of the owner.
    address addr;
    // Stores the start time of ownership with minimal overhead for tokenomics.
    uint64 startTimestamp;
    // Whether the token has been burned.
    bool burned;
    // Arbitrary data similar to `startTimestamp` that can be set via {_extraData}.
    uint24 extraData;
  }

  // =============================================================
  //                         TOKEN COUNTERS
  // =============================================================

  /**
   * @dev Returns the total number of tokens in existence.
   * Burned tokens will reduce the count.
   * To get the total number of tokens minted, please see {_totalMinted}.
   */
  function totalSupply() external view returns (uint256);

  // =============================================================
  //                            IERC165
  // =============================================================

  /**
   * @dev Returns true if this contract implements the interface defined by
   * `interfaceId`. See the corresponding
   * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
   * to learn more about how these ids are created.
   *
   * This function call must use less than 30000 gas.
   */
  function supportsInterface(bytes4 interfaceId) external view returns (bool);

  // =============================================================
  //                            IERC721
  // =============================================================

  /**
   * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
   */
  event Transfer(
    address indexed from,
    address indexed to,
    uint256 indexed tokenId
  );

  /**
   * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
   */
  event Approval(
    address indexed owner,
    address indexed approved,
    uint256 indexed tokenId
  );

  /**
   * @dev Emitted when `owner` enables or disables
   * (`approved`) `operator` to manage all of its assets.
   */
  event ApprovalForAll(
    address indexed owner,
    address indexed operator,
    bool approved
  );

  /**
   * @dev Returns the number of tokens in `owner`'s account.
   */
  function balanceOf(address owner) external view returns (uint256 balance);

  /**
   * @dev Returns the owner of the `tokenId` token.
   *
   * Requirements:
   *
   * - `tokenId` must exist.
   */
  function ownerOf(uint256 tokenId) external view returns (address owner);

  /**
   * @dev Safely transfers `tokenId` token from `from` to `to`,
   * checking first that contract recipients are aware of the ERC721 protocol
   * to prevent tokens from being forever locked.
   *
   * Requirements:
   *
   * - `from` cannot be the zero address.
   * - `to` cannot be the zero address.
   * - `tokenId` token must exist and be owned by `from`.
   * - If the caller is not `from`, it must be have been allowed to move
   * this token by either {approve} or {setApprovalForAll}.
   * - If `to` refers to a smart contract, it must implement
   * {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
   *
   * Emits a {Transfer} event.
   */
  function safeTransferFrom(
    address from,
    address to,
    uint256 tokenId,
    bytes calldata data
  ) external payable;

  /**
   * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
   */
  function safeTransferFrom(
    address from,
    address to,
    uint256 tokenId
  ) external payable;

  /**
   * @dev Transfers `tokenId` from `from` to `to`.
   *
   * WARNING: Usage of this method is discouraged, use {safeTransferFrom}
   * whenever possible.
   *
   * Requirements:
   *
   * - `from` cannot be the zero address.
   * - `to` cannot be the zero address.
   * - `tokenId` token must be owned by `from`.
   * - If the caller is not `from`, it must be approved to move this token
   * by either {approve} or {setApprovalForAll}.
   *
   * Emits a {Transfer} event.
   */
  function transferFrom(
    address from,
    address to,
    uint256 tokenId
  ) external payable;

  /**
   * @dev Gives permission to `to` to transfer `tokenId` token to another account.
   * The approval is cleared when the token is transferred.
   *
   * Only a single account can be approved at a time, so approving the
   * zero address clears previous approvals.
   *
   * Requirements:
   *
   * - The caller must own the token or be an approved operator.
   * - `tokenId` must exist.
   *
   * Emits an {Approval} event.
   */
  function approve(address to, uint256 tokenId) external payable;

  /**
   * @dev Approve or remove `operator` as an operator for the caller.
   * Operators can call {transferFrom} or {safeTransferFrom}
   * for any token owned by the caller.
   *
   * Requirements:
   *
   * - The `operator` cannot be the caller.
   *
   * Emits an {ApprovalForAll} event.
   */
  function setApprovalForAll(address operator, bool _approved) external;

  /**
   * @dev Returns the account approved for `tokenId` token.
   *
   * Requirements:
   *
   * - `tokenId` must exist.
   */
  function getApproved(
    uint256 tokenId
  ) external view returns (address operator);

  /**
   * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
   *
   * See {setApprovalForAll}.
   */
  function isApprovedForAll(
    address owner,
    address operator
  ) external view returns (bool);

  // =============================================================
  //                        IERC721Metadata
  // =============================================================

  /**
   * @dev Returns the token collection name.
   */
  function name() external view returns (string memory);

  /**
   * @dev Returns the token collection symbol.
   */
  function symbol() external view returns (string memory);

  /**
   * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
   */
  function tokenURI(uint256 tokenId) external view returns (string memory);

  // =============================================================
  //                           IERC2309
  // =============================================================

  /**
   * @dev Emitted when tokens in `fromTokenId` to `toTokenId`
   * (inclusive) is transferred from `from` to `to`, as defined in the
   * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) standard.
   *
   * See {_mintERC2309} for more details.
   */
  event ConsecutiveTransfer(
    uint256 indexed fromTokenId,
    uint256 toTokenId,
    address indexed from,
    address indexed to
  );
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "evmVersion": "paris",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract ABI

[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ApprovalCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"ApprovalQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"BalanceQueryForZeroAddress","type":"error"},{"inputs":[],"name":"MintERC2309QuantityExceedsLimit","type":"error"},{"inputs":[],"name":"MintToZeroAddress","type":"error"},{"inputs":[],"name":"MintZeroQuantity","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"OwnerQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"OwnershipNotInitializedForExtraData","type":"error"},{"inputs":[],"name":"TransferCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferToNonERC721ReceiverImplementer","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"inputs":[],"name":"URIQueryForNonexistentToken","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"fromTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"toTokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"ConsecutiveTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"newMerkleRoot","type":"bytes32"}],"name":"MerkleRootUpdated","type":"event"},{"anonymous":false,"inputs":[],"name":"MintingLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[],"name":"URILocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"oldURISuffix","type":"string"},{"indexed":false,"internalType":"string","name":"newURISuffix","type":"string"}],"name":"URISuffixUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"oldURI","type":"string"},{"indexed":false,"internalType":"string","name":"newURI","type":"string"}],"name":"URIUpdated","type":"event"},{"anonymous":false,"inputs":[],"name":"UniqueMetadataBoolUpdated","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[{"internalType":"bytes32[]","name":"proof_","type":"bytes32[]"},{"internalType":"bytes32","name":"leafHash_","type":"bytes32"}],"name":"addressIsInMerkleTree","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId_","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"burnable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"tokenTypeId","type":"uint256"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"internalType":"struct IERC721CC.MintRequest[]","name":"mintRequests_","type":"tuple[]"}],"name":"communityMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"tokenTypeId","type":"uint256"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"internalType":"struct IERC721CC.MintRequest[]","name":"mintRequests_","type":"tuple[]"}],"name":"fixedMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"minter_","type":"address"},{"internalType":"uint256","name":"tokenTypeId_","type":"uint256"}],"name":"getAddressHasMinted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllTokenTypes","outputs":[{"internalType":"uint16[]","name":"","type":"uint16[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMerkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"string","name":"baseURI_","type":"string"},{"internalType":"uint256","name":"maxSupply_","type":"uint256"},{"internalType":"bool[]","name":"switches_","type":"bool[]"},{"internalType":"bytes","name":"initialArgs_","type":"bytes"}],"name":"initialise","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initialised","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes14","name":"confirm_","type":"bytes14"}],"name":"lockMinting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes14","name":"confirm_","type":"bytes14"}],"name":"lockURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lockedMinting","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockedURI","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxSupply","outputs":[{"internalType":"uint104","name":"","type":"uint104"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenIdToTypeId","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"transferable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"uniqueMetadata","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"uri_","type":"string"}],"name":"updateBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"newMerkleRoot_","type":"bytes32"}],"name":"updateMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"tokenTypeId","type":"uint256"},{"internalType":"string","name":"suffix","type":"string"}],"internalType":"struct ICommunityCollection.URISuffixes[]","name":"uriSuffixes_","type":"tuple[]"}],"name":"updateURISuffixes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"uniqueMetadata_","type":"bool"}],"name":"updateUniqueMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A token is a representation of an on-chain or off-chain asset. The token page shows information such as price, total supply, holders, transfers and social links. Learn more about this page in our Knowledge Base.