// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../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.
 *
 * By default, the owner account will be the one that deploys the contract. 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;

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @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 {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @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 {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _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: @openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Metadata is IERC721 {
    /**
     * @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);
}

// File: @openzeppelin/contracts/token/ERC721/IERC721.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @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`.
     *
     * 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 calldata data) external;

    /**
     * @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 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) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * 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;

    /**
     * @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;

    /**
     * @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);
}

// File: @openzeppelin/contracts/token/ERC721/IERC721Receiver.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

// File: @openzeppelin/contracts/utils/Address.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

// File: @openzeppelin/contracts/utils/Base64.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Base64.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides a set of functions to operate with Base64 strings.
 *
 * _Available since v4.5._
 */
library Base64 {
    /**
     * @dev Base64 Encoding/Decoding Table
     */
    string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    /**
     * @dev Converts a `bytes` to its Bytes64 `string` representation.
     */
    function encode(bytes memory data) internal pure returns (string memory) {
        /**
         * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
         * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
         */
        if (data.length == 0) return "";

        // Loads the table into memory
        string memory table = _TABLE;

        // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
        // and split into 4 numbers of 6 bits.
        // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
        // - `data.length + 2`  -> Round up
        // - `/ 3`              -> Number of 3-bytes chunks
        // - `4 *`              -> 4 characters for each chunk
        string memory result = new string(4 * ((data.length + 2) / 3));

        /// @solidity memory-safe-assembly
        assembly {
            // Prepare the lookup table (skip the first "length" byte)
            let tablePtr := add(table, 1)

            // Prepare result pointer, jump over length
            let resultPtr := add(result, 32)

            // Run over the input, 3 bytes at a time
            for {
                let dataPtr := data
                let endPtr := add(data, mload(data))
            } lt(dataPtr, endPtr) {

            } {
                // Advance 3 bytes
                dataPtr := add(dataPtr, 3)
                let input := mload(dataPtr)

                // To write each character, shift the 3 bytes (18 bits) chunk
                // 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
                // and apply logical AND with 0x3F which is the number of
                // the previous character in the ASCII table prior to the Base64 Table
                // The result is then added to the table to get the character to write,
                // and finally write it in the result pointer but with a left shift
                // of 256 (1 byte) - 8 (1 ASCII char) = 248 bits

                mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance
            }

            // When data `bytes` is not exactly 3 bytes long
            // it is padded with `=` characters at the end
            switch mod(mload(data), 3)
            case 1 {
                mstore8(sub(resultPtr, 1), 0x3d)
                mstore8(sub(resultPtr, 2), 0x3d)
            }
            case 2 {
                mstore8(sub(resultPtr, 1), 0x3d)
            }
        }

        return result;
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @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;
    }
}

// File: @openzeppelin/contracts/utils/introspection/ERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// File: @openzeppelin/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// File: @openzeppelin/contracts/utils/math/Math.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

// File: @openzeppelin/contracts/utils/math/SignedMath.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

// File: @openzeppelin/contracts/utils/Strings.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

// File: contracts/Honeycombs.sol



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

import "@openzeppelin/contracts/access/Ownable.sol";

import "./interfaces/IHoneycombs.sol";
import "./libraries/HoneycombsArt.sol";
import "./libraries/HoneycombsMetadata.sol";
import "./libraries/Utilities.sol";
import "./standards/HONEYCOMBS721.sol";

/**
@title  Honeycombs
@author Gaurang Patel (adapted from checks.vv contracts)
@notice The only way you can conquer me is through love and there I am gladly conquered.
*/
contract Honeycombs is IHoneycombs, HONEYCOMBS721, Ownable {
    /// @dev We use this database for persistent storage.
    Honeycombs honeycombs;

    uint256 public constant MAX_SUPPLY = 10000; // Maximum supply of Honeycombs
    uint256 public constant MINT_PRICE = 0.1 ether; // Price to mint one Honeycomb
    uint256 public constant MAX_MINT_PER_ADDRESS = 5; // Maximum NFTs per wallet address
    uint256 public constant AUTO_RESERVE_FREQUENCY = 100; // Frequency of auto-reserve
    address public reserveAddress1; // First address to auto reserve Honeycombs for
    address public reserveAddress2; // Second address to auto reserve Honeycombs for

    // Number of mints per address
    mapping(address => uint256) private _mintedCounts;

    /// @dev Initializes the Honeycombs contract.
    constructor() {
        honeycombs.day0 = uint32(block.timestamp);
        honeycombs.epoch = 1;
        reserveAddress1 = 0x895e58968819E821465857CDbE33B82027527747; // artist
        reserveAddress2 = 0x1E29711abbc2E350e47D5963C5AC5470b59a1aa4; // fellowship
    }

    /// @notice Mint honeycombs.
    /// @param numberOfTokens The number of tokens to mint.
    /// @param recipient The address to receive the tokens.
    function mint(uint256 numberOfTokens, address recipient) public payable {
        // Check whether mint is allowed.
        if (numberOfTokens < 1 || numberOfTokens > MAX_MINT_PER_ADDRESS) revert NotAllowed();
        if (honeycombs.minted + numberOfTokens > MAX_SUPPLY) revert MaxSupplyReached();
        if (msg.value != MINT_PRICE * numberOfTokens) revert NotExactEth();
        if (_mintedCounts[msg.sender] + numberOfTokens > MAX_MINT_PER_ADDRESS) revert MaxMintPerAddressReached();

        // Calculate the total value to allocate to reserve address 2 (20%).
        uint256 reserve2Value = (msg.value * 20) / 100;

        // Initialize new epoch / resolve previous epoch.
        resolveEpochIfNecessary();

        // Loop through and mint each Honeycomb.
        for (uint256 i = 0; i < numberOfTokens; ) {
            // Check for auto reserving honeycombs (first and second out of every 100).
            if (honeycombs.minted % AUTO_RESERVE_FREQUENCY == 0 && honeycombs.minted != MAX_SUPPLY) {
                uint32 reserve1TokenId = ++honeycombs.minted;
                uint32 reserve2TokenId = ++honeycombs.minted;

                // Initialize Honeycombs.
                StoredHoneycomb storage honeycomb1 = honeycombs.all[reserve1TokenId];
                honeycomb1.day = Utilities.day(honeycombs.day0, block.timestamp);
                honeycomb1.epoch = uint32(honeycombs.epoch);
                honeycomb1.seed = uint16(reserve1TokenId);

                StoredHoneycomb storage honeycomb2 = honeycombs.all[reserve2TokenId];
                honeycomb2.day = Utilities.day(honeycombs.day0, block.timestamp);
                honeycomb2.epoch = uint32(honeycombs.epoch);
                honeycomb2.seed = uint16(reserve2TokenId);

                // Mint to reserve addresses.
                _safeMint(reserveAddress1, reserve1TokenId);
                _safeMint(reserveAddress2, reserve2TokenId);
            }

            // Increment minted counters.
            ++honeycombs.minted;
            ++_mintedCounts[msg.sender];

            // Initialize our Honeycomb.
            StoredHoneycomb storage honeycomb = honeycombs.all[honeycombs.minted];
            honeycomb.day = Utilities.day(honeycombs.day0, block.timestamp);
            honeycomb.epoch = uint32(honeycombs.epoch);
            honeycomb.seed = uint16(honeycombs.minted);

            // Mint the original.
            // If we're minting to a vault, transfer it there.
            if (msg.sender != recipient) {
                _safeMintVia(recipient, msg.sender, honeycombs.minted);
            } else {
                _safeMint(msg.sender, honeycombs.minted);
            }

            unchecked {
                ++i;
            }
        }

        // Transfer the reserve value to reserve address 2.
        payable(reserveAddress2).transfer(reserve2Value);
    }

    /// @notice Burn a honeycomb.
    /// @param tokenId The token ID to burn.
    /// @dev A common purpose burn method.
    function burn(uint256 tokenId) public {
        if (!_isApprovedOrOwner(msg.sender, tokenId)) {
            revert NotAllowed();
        }

        // Keep track of supply.
        unchecked {
            ++honeycombs.burned;
        }

        // Perform the burn.
        _burn(tokenId);
    }

    /// @notice Initializes and closes epochs.
    /// @dev Based on the commit-reveal scheme proposed by MouseDev.
    function resolveEpochIfNecessary() public {
        Epoch storage currentEpoch = honeycombs.epochs[honeycombs.epoch];

        if (
            // If epoch has not been committed,
            currentEpoch.committed == false ||
            // Or the reveal commitment timed out.
            (currentEpoch.revealed == false && currentEpoch.revealBlock < block.number - 256)
        ) {
            // This means the epoch has not been committed, OR the epoch was committed but has expired.
            // Set committed to true, and record the reveal block:
            currentEpoch.revealBlock = uint64(block.number + 50);
            currentEpoch.committed = true;
        } else if (block.number > currentEpoch.revealBlock) {
            // Epoch has been committed and is within range to be revealed.
            // Set its randomness to the target block hash.
            currentEpoch.randomness = uint128(
                uint256(keccak256(abi.encodePacked(blockhash(currentEpoch.revealBlock), block.difficulty))) %
                    (2 ** 128 - 1)
            );
            currentEpoch.revealed = true;

            // Notify DApps about the new epoch.
            emit NewEpoch(honeycombs.epoch, currentEpoch.revealBlock);

            // Initialize the next epoch
            honeycombs.epoch++;
            resolveEpochIfNecessary();
        }
    }

    /// @notice Withdraw funds (only callable by the owner).
    /// @param amount The amount to withdraw.
    function withdraw(uint256 amount) public onlyOwner {
        if (address(this).balance < amount) {
            revert NotAllowed();
        }
        payable(owner()).transfer(amount);
    }


    /// @notice The identifier of the current epoch
    function getEpoch() public view returns (uint256) {
        return honeycombs.epoch;
    }

    /// @notice Get the data for a given epoch
    /// @param index The identifier of the epoch to fetch
    function getEpochData(uint256 index) public view returns (Epoch memory) {
        return honeycombs.epochs[index];
    }

    /// @notice Get a specific honeycomb.
    /// @param tokenId The token ID to fetch.
    /// @dev Consider using the HoneycombsArt Library directly.
    function getHoneycomb(uint256 tokenId) external view returns (Honeycomb memory honeycomb) {
        return HoneycombsArt.generateHoneycomb(honeycombs, tokenId);
    }

    /// @notice Render the SVG for a given token.
    /// @param tokenId The token to render.
    /// @dev Consider using the HoneycombsArt Library directly.
    function svg(uint256 tokenId) external view returns (string memory) {
        return string(HoneycombsArt.generateHoneycomb(honeycombs, tokenId).svg);
    }

    /// @notice Get the metadata for a given token.
    /// @param tokenId The token to render.
    /// @dev Consider using the HoneycombsMetadata Library directly.
    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        _requireMinted(tokenId);

        return HoneycombsMetadata.tokenURI(honeycombs, tokenId);
    }

    /// @notice Returns how many tokens this contract manages.
    function totalSupply() public view returns (uint256) {
        return honeycombs.minted - honeycombs.burned;
    }

    /// @notice Returns how many tokens have been minted.
    function minted() public view returns (uint256) {
        return honeycombs.minted;
    }
}

// File: contracts/interfaces/IERC4906.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/CHECKS721.sol)

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";

/// @title EIP-721 Metadata Update Extension
interface IERC4906 is IERC165, IERC721 {
    /// @dev This event emits when the metadata of a token is changed.
    /// Third-party platforms such as NFT marketplaces can listen to
    /// the event and auto-update the tokens in their apps.
    event MetadataUpdate(uint256 _tokenId);
}

// File: contracts/interfaces/IHoneycombs.sol



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

interface IHoneycombs {
    /// @dev The minimal honeycomb data stored on-chain, the rest is generated.
    struct StoredHoneycomb {
        uint32 epoch; // Each honeycomb is revealed in an epoch
        uint24 day; // The days since token was created
        uint16 seed; // A unique identifier (this is the token ID since it is pre-reveal)
    }

    struct Honeycomb {
        StoredHoneycomb stored; // We carry over the honeycomb from storage
        bool isRevealed; // Whether the honeycomb is revealed
        uint256 seed; // The instantiated seed for pseudo-randomisation (post-reveal)
        bytes svg; // final svg for the honeycomb
        Canvas canvas; // all data relevant to the canvas
        BaseHexagon baseHexagon; // all data relevant to the base hexagon
        Grid grid; // all data relevant to the grid
        Gradients gradients; // all data relevant to the gradients
    }

    struct Honeycombs {
        mapping(uint256 => StoredHoneycomb) all; // All honeycombs
        uint32 maxSupply; // The maximum number of honeycombs that can be minted
        uint32 minted; // The number of honeycombs that have been minted
        uint32 burned; // The number of honeycombs that have been burned
        uint32 day0; // Marks the start of this journey
        mapping(uint256 => Epoch) epochs; // All epochs
        uint256 epoch; // The current epoch index
    }

    struct Canvas {
        string color; // background color of canvas
        uint16 size; // size or length of canvas in user units (pixels)
        uint16 hexagonSize; // size or length of hexagon in user units (pixels)
        uint16 maxHexagonsPerLine; // max number of hexagons per line
    }

    struct BaseHexagon {
        string path; // path of base hexagon
        string fillColor; // fill color of base hexagon
        uint8 strokeWidth; // stroke width size in user units (pixels)
        uint8 hexagonType; // type of base hexagon, i.e. flat or pointy
    }

    struct Grid {
        bytes hexagonsSvg; // final svg for all hexagons
        bytes svg; // final svg for the grid
        uint16 gridX; // x coordinate of the grid
        uint16 gridY; // y coordinate of the grid
        uint16 rowDistance; // distance between rows in user units (pixels)
        uint16 columnDistance; // distance between columns in user units (pixels)
        uint16 rotation; // rotation of entire shape in degrees
        uint8 shape; // shape of the grid, i.e. triangle, diamond, hexagon, random
        uint8 totalGradients; // number of gradients required based on the grid size and shape
        uint8 rows; // number of rows in the grid
        uint8 longestRowCount; // largest row size in the grid for centering purposes
    }

    struct Gradients {
        bytes svg; // final svg for the gradients
        uint16 duration; // duration of animation in seconds
        uint8 direction; // direction of animation, i.e. forward or backward
        uint8 chrome; // max number of colors in all the gradients, aka chrome
    }

    struct Epoch {
        uint128 randomness; // The source of randomness for tokens from this epoch
        uint64 revealBlock; // The block at which this epoch was / is revealed
        bool committed; // Whether the epoch has been instantiated
        bool revealed; // Whether the epoch has been revealed
    }

    event NewEpoch(uint256 indexed epoch, uint64 indexed revealBlock);

    error NotAllowed();
    error MaxSupplyReached();
    error NotExactEth();
    error MaxMintPerAddressReached();
}

// File: contracts/libraries/Colors.sol



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

/**
@title  Colors
@notice The colors of honeycombs.
*/
library Colors {
    /// @dev These are sorted in a gradient.
    function COLORS() public pure returns (string[46] memory) {
        return [
            "FF005D",
            "FF0040",
            "FF0011",
            "FF0D00",
            "FF3300",
            "FF4C00",
            "FF6600",
            "FF7700",
            "FF8800",
            "FF9900",
            "FFB300",
            "FFCC00",
            "FFE600",
            "FFF700",
            "FFFF00",
            "F6FF00",
            "EEFF00",
            "D4FF00",
            "B3FF00",
            "99FF00",
            "80FF00",
            "62FF00",
            "00FF11",
            "00FF80",
            "00FFBF",
            "00FFEE",
            "00F7FF",
            "00E6FF",
            "00C3FF",
            "0099FF",
            "0077FF",
            "0055FF",
            "0033FF",
            "3300FF",
            "5500FF",
            "6600FF",
            "7B00FF",
            "9000FF",
            "AA00FF",
            "BB00FF",
            "D400FF",
            "EE00FF",
            "FB00FF",
            "FF00EA",
            "FF00CC",
            "FF00A2"
        ];
    }
}

// File: contracts/libraries/GradientsArt.sol



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

import "../interfaces/IHoneycombs.sol";
import "./Utilities.sol";
import "./Colors.sol";

/**
@title  GradientsArt
@notice Generates the gradients for a given Honeycomb.
*/
library GradientsArt {
    enum HEXAGON_TYPE { FLAT, POINTY } // prettier-ignore
    enum SHAPE { TRIANGLE, DIAMOND, HEXAGON, RANDOM } // prettier-ignore

        /// @dev Get from different chromes or max primary colors. Corresponds to chrome trait in HoneycombsMetadata.sol.
    function getChrome(uint8 index) public pure returns (uint8) {
        return uint8([1, 2, 3, 4, 5, 6, Colors.COLORS().length][index]);
    }

    /// @dev Get from different animation durations in seconds. Corresponds to duration trait in HoneycombsMetadata.sol.
    function getDuration(uint16 index) public pure returns (uint16) {
        return uint16([10, 40, 80, 240][index]);
    }

    /// @dev Get the linear gradient's svg.
    /// @param data The gradient data.
    function getLinearGradientSvg(GradientData memory data) public pure returns (bytes memory) {
        // prettier-ignore
        bytes memory svg = abi.encodePacked(
            '<linearGradient id="gradient', Utilities.uint2str(data.gradientId), '" x1="0%" x2="0%" y1="', 
                    Utilities.uint2str(data.y1), '%" y2="', Utilities.uint2str(data.y2), '%">',
                '<stop stop-color="', data.stop1.color, '">',
                    '<animate attributeName="stop-color" values="', data.stop1.animationColorValues, '" dur="', 
                        Utilities.uint2str(data.duration), 's" begin="animation.begin" repeatCount="indefinite" />',
                '</stop>',
                '<stop offset="0.', Utilities.uint2str(data.offset), '" stop-color="', data.stop2.color, '">',
                    '<animate attributeName="stop-color" values="', data.stop2.animationColorValues, '" dur="', 
                        Utilities.uint2str(data.duration), 's" begin="animation.begin" repeatCount="indefinite" />',
                '</stop>',
            '</linearGradient>'
        );

        return svg;
    }

    /// @dev Get the stop for a linear gradient.
    /// @param honeycomb The honeycomb data used for rendering.
    /// @param stopCount The current stop count - used for seeding the random number generator.
    function getLinearGradientStopSvg(
        IHoneycombs.Honeycomb memory honeycomb,
        uint8 stopCount
    ) public pure returns (GradientStop memory) {
        GradientStop memory stop;
        string[46] memory allColors = Colors.COLORS();

        // Get random stop color.
        uint256 currentIndex = Utilities.random(
            honeycomb.seed,
            abi.encodePacked("linearGradientStop", Utilities.uint2str(stopCount)),
            allColors.length
        );
        stop.color = abi.encodePacked("#", allColors[currentIndex]);

        bytes memory values;
        // Add the initial color.
        values = abi.encodePacked(values, stop.color, ";");

        // Get all animation values based on the direction.
        bool forwardDirection = honeycomb.gradients.direction == 0;

        // We pick 14 more different colors for the gradient.
        uint8 count = 14;
        for (uint256 i = 0; i <= (count * 2) - 2; ) {
            bool isFirstHalf = i < count;

            // For the first half, follow the direction. For the second half, reverse the direction.
            if (isFirstHalf == forwardDirection) {
                currentIndex = (currentIndex + 2) % allColors.length;
            } else {
                currentIndex = (currentIndex + allColors.length - 2) % allColors.length;
            }

            values = abi.encodePacked(values, "#", allColors[currentIndex], ";");
            unchecked {
                ++i;
            }
        }

        // Add the last color.
        stop.animationColorValues = abi.encodePacked(values, stop.color);
        return stop;
    }

    /// @dev Get all gradients data, particularly the svg.
    /// @param honeycomb The honeycomb data used for rendering.
    function generateGradientsSvg(IHoneycombs.Honeycomb memory honeycomb) public pure returns (bytes memory) {
        bytes memory svg;

        // Initialize array of stops (id => svgString) for reuse once we reach the max color count.
        GradientStop[] memory stops = new GradientStop[](honeycomb.grid.totalGradients + 1);

        uint8 stopCount;
        GradientStop memory prevStop = getLinearGradientStopSvg(honeycomb, stopCount);
        stops[stopCount] = prevStop;
        ++stopCount;

        // Loop through all gradients and generate the svg.
        for (uint256 i; i < honeycomb.grid.totalGradients; ) {
            GradientStop memory stop;

            // Get next stop.
            if (stopCount < honeycomb.gradients.chrome) {
                stop = getLinearGradientStopSvg(honeycomb, stopCount);
                stops[stopCount] = stop;
                unchecked {
                    ++stopCount;
                }
            } else {
                // Randomly select a stop from existing ones.
                stop = stops[
                    Utilities.random(honeycomb.seed, abi.encodePacked("stop", Utilities.uint2str(i)), stopCount)
                ];
            }

            // Get gradients svg based on the base hexagon type.
            if (honeycomb.baseHexagon.hexagonType == uint8(HEXAGON_TYPE.POINTY)) {
                GradientData memory gradientData;
                gradientData.stop1 = prevStop;
                gradientData.stop2 = stop;
                gradientData.duration = honeycomb.gradients.duration;
                gradientData.gradientId = uint8(i + 1);
                gradientData.y1 = 25;
                gradientData.y2 = 81;
                gradientData.offset = 72;
                bytes memory gradientSvg = getLinearGradientSvg(gradientData);

                // Append gradient to svg, update previous stop, and increment index.
                svg = abi.encodePacked(svg, gradientSvg);
                prevStop = stop;
                unchecked {
                    ++i;
                }
            } else if (honeycomb.baseHexagon.hexagonType == uint8(HEXAGON_TYPE.FLAT)) {
                // Flat tops require two gradients.
                GradientData memory gradientData1;
                gradientData1.stop1 = prevStop;
                gradientData1.stop2 = stop;
                gradientData1.duration = honeycomb.gradients.duration;
                gradientData1.gradientId = uint8(i + 1);
                gradientData1.y1 = 50;
                gradientData1.y2 = 100;
                gradientData1.offset = 72;
                bytes memory gradient1Svg = getLinearGradientSvg(gradientData1);

                if (i == honeycomb.grid.totalGradients - 1) {
                    // If this is the last gradient, we don't need to generate the second gradient.
                    svg = abi.encodePacked(svg, gradient1Svg);
                    break;
                }

                GradientData memory gradientData2;
                gradientData2.stop1 = prevStop;
                gradientData2.stop2 = stop;
                gradientData2.duration = honeycomb.gradients.duration;
                gradientData2.gradientId = uint8(i + 2);
                gradientData2.y1 = 4;
                gradientData2.y2 = 100;
                gradientData2.offset = 30;
                bytes memory gradient2Svg = getLinearGradientSvg(gradientData2);

                // Append both gradients to svg, update previous stop, and increment index.
                svg = abi.encodePacked(svg, gradient1Svg, gradient2Svg);
                prevStop = stop;
                unchecked {
                    i += 2;
                }
            }
        }

        return svg;
    }
}

/// @dev All internal data relevant to a gradient stop.
struct GradientStop {
    bytes color; // color of the gradient stop
    bytes animationColorValues; // color values for the animation
}

/// @dev All additional internal data for rendering a gradient svg string.
struct GradientData {
    GradientStop stop1; // first gradient stop
    GradientStop stop2; // second gradient stop
    uint16 duration; // duration of the animation
    uint8 gradientId; // id of the gradient
    uint8 y1; // y1 of the gradient
    uint8 y2; // y2 of the gradient
    uint8 offset; // offset of the gradient
}

// File: contracts/libraries/GridArt.sol



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

import "../interfaces/IHoneycombs.sol";
import "./Utilities.sol";

/**
@title  GridArt
@notice Generates the grid for a given Honeycomb.
*/
library GridArt {
    enum HEXAGON_TYPE { FLAT, POINTY } // prettier-ignore
    enum SHAPE { TRIANGLE, DIAMOND, HEXAGON, RANDOM } // prettier-ignore

    /// @dev The paths for a 72x72 px hexagon.
    function getHexagonPath(uint8 pathType) public pure returns (string memory path) {
        if (pathType == uint8(HEXAGON_TYPE.FLAT)) {
            return "M22.2472 7.32309L4.82457 37.5C3.93141 39.047 3.93141 40.953 4.82457 42.5L22.2472 72.6769C23.1404 74.2239 24.791 75.1769 26.5774 75.1769H61.4226C63.209 75.1769 64.8596 74.2239 65.7528 72.6769L83.1754 42.5C84.0686 40.953 84.0686 39.047 83.1754 37.5L65.7528 7.32309C64.8596 5.77608 63.209 4.82309 61.4226 4.82309H26.5774C24.791 4.82309 23.1404 5.77608 22.2472 7.32309Z"; // prettier-ignore
        } else if (pathType == uint8(HEXAGON_TYPE.POINTY)) {
            return "M72.6769 22.2472L42.5 4.82457C40.953 3.93141 39.047 3.93141 37.5 4.82457L7.32309 22.2472C5.77608 23.1404 4.82309 24.791 4.82309 26.5774V61.4226C4.82309 63.209 5.77608 64.8596 7.32309 65.7528L37.5 83.1754C39.047 84.0686 40.953 84.0686 42.5 83.1754L72.6769 65.7528C74.2239 64.8596 75.1769 63.209 75.1769 61.4226V26.5774C75.1769 24.791 74.2239 23.1404 72.6769 22.2472Z"; // prettier-ignore
        }
    }

        /// @dev Get hexagon from given grid and hexagon properties.
    /// @param grid The grid metadata.
    /// @param xIndex The x index in the grid.
    /// @param yIndex The y index in the grid.
    /// @param gradientId The gradient id for the hexagon.
    function getUpdatedHexagonsSvg(
        IHoneycombs.Grid memory grid,
        uint16 xIndex,
        uint16 yIndex,
        uint16 gradientId
    ) public pure returns (bytes memory) {
        uint16 x = grid.gridX + xIndex * grid.columnDistance;
        uint16 y = grid.gridY + yIndex * grid.rowDistance;

        // prettier-ignore
        return abi.encodePacked(grid.hexagonsSvg, abi.encodePacked(
            '<use href="#hexagon" stroke="url(#gradient', Utilities.uint2str(gradientId), ')" ',
                'x="', Utilities.uint2str(x), '" y="', Utilities.uint2str(y), '"',
            '/>'
        ));
    }

    /// @dev Add positioning to the grid (for centering on canvas).
    /// @dev Note this function appends attributes to grid object, so returned object has original grid + positioning.
    /// @param honeycomb The honeycomb data used for rendering.
    /// @param grid The grid metadata.
    function addGridPositioning(
        IHoneycombs.Honeycomb memory honeycomb,
        IHoneycombs.Grid memory grid
    ) public pure returns (IHoneycombs.Grid memory) {
        // Compute grid properties.
        grid.rowDistance = ((3 * honeycomb.canvas.hexagonSize) / 4) + 7; // 7 is a relatively arbitrary buffer
        grid.columnDistance = honeycomb.canvas.hexagonSize / 2 - 1;
        uint16 gridHeight = honeycomb.canvas.hexagonSize + 7 + ((grid.rows - 1) * grid.rowDistance);
        uint16 gridWidth = grid.longestRowCount * (honeycomb.canvas.hexagonSize - 2);

        /**
         * Swap variables if it is a flat top hexagon (this math assumes pointy top as default). Rotating a flat top
         * hexagon 90 degrees clockwise results in a pointy top hexagon. This effectively swaps the x and y axis.
         */
        if (honeycomb.baseHexagon.hexagonType == uint8(HEXAGON_TYPE.FLAT)) {
            (grid.rowDistance, grid.columnDistance) = Utilities.swap(grid.rowDistance, grid.columnDistance);
            (gridWidth, gridHeight) = Utilities.swap(gridWidth, gridHeight);
        }

        // Compute grid positioning.
        grid.gridX = (honeycomb.canvas.size - gridWidth) / 2;
        grid.gridY = (honeycomb.canvas.size - gridHeight) / 2;

        return grid;
    }

    /// @dev Get the honeycomb grid for a random shape.
    /// @dev Note: can only be called for pointy tops (flat tops are not supported as they would be redundant).
    /// @param honeycomb The honeycomb data used for rendering.
    function getRandomGrid(IHoneycombs.Honeycomb memory honeycomb) public pure returns (IHoneycombs.Grid memory) {
        IHoneycombs.Grid memory grid;

        // Get random rows from 1 to honeycomb.canvas.maxHexagonsPerline.
        grid.rows = uint8(Utilities.random(honeycomb.seed, "rows", honeycomb.canvas.maxHexagonsPerLine) + 1);

        // Get random hexagons in each row from 1 to honeycomb.canvas.maxHexagonsPerLine - 1.
        uint8[] memory hexagonsInRow = new uint8[](grid.rows);
        for (uint8 i; i < grid.rows; ) {
            hexagonsInRow[i] =
                uint8(Utilities.random(
                    honeycomb.seed,
                    abi.encodePacked("hexagonsInRow", Utilities.uint2str(i)),
                    honeycomb.canvas.maxHexagonsPerLine - 1
                ) + 1); // prettier-ignore
            grid.longestRowCount = Utilities.max(hexagonsInRow[i], grid.longestRowCount);

            unchecked {
                ++i;
            }
        }

        // Determine positioning of entire grid, which is based on the longest row.
        grid = addGridPositioning(honeycomb, grid); // appends to grid object

        int8 lastRowEvenOdd = -1; // Helps avoid overlapping hexagons: -1 = unset, 0 = even, 1 = odd
        // Create random grid. Only working with pointy tops for simplicity.
        for (uint8 i; i < grid.rows; ) {
            uint8 firstX = grid.longestRowCount - hexagonsInRow[i];

            // Increment firstX if last row's evenness/oddness is same as this rows and update with current.
            if (lastRowEvenOdd == int8(firstX % 2)) ++firstX;
            lastRowEvenOdd = int8(firstX % 2);

            // Assign indexes for each hexagon.
            for (uint8 j; j < hexagonsInRow[i]; ) {
                uint8 xIndex = firstX + (j * 2);
                grid.hexagonsSvg = getUpdatedHexagonsSvg(grid, xIndex, i, i + 1);
                unchecked {
                    ++j;
                }
            }

            unchecked {
                ++i;
            }
        }

        grid.totalGradients = grid.rows;
        return grid;
    }

    /// @dev Get the honeycomb grid for a hexagon shape.
    /// @param honeycomb The honeycomb data used for rendering.
    function getHexagonGrid(IHoneycombs.Honeycomb memory honeycomb) public pure returns (IHoneycombs.Grid memory) {
        IHoneycombs.Grid memory grid;

        // Get random rows from 3 to honeycomb.canvas.maxHexagonsPerLine, only odd.
        grid.rows = uint8(
            Utilities.random(honeycomb.seed, "rows", (honeycomb.canvas.maxHexagonsPerLine / 2) - 1) * 2 + 3
        );

        // Determine positioning of entire grid, which is based on the longest row.
        grid.longestRowCount = grid.rows;
        grid = addGridPositioning(honeycomb, grid); // appends to grid object

        // Create grid based on hexagon base type.
        if (honeycomb.baseHexagon.hexagonType == uint8(HEXAGON_TYPE.POINTY)) {
            grid.totalGradients = grid.rows;

            for (uint8 i; i < grid.rows; ) {
                // Compute hexagons in row.
                uint8 hexagonsInRow = grid.rows - Utilities.absDiff(grid.rows / 2, i);

                // Assign indexes for each hexagon.
                for (uint8 j; j < hexagonsInRow; ) {
                    uint8 xIndex = (grid.rows - hexagonsInRow) + (j * 2);
                    grid.hexagonsSvg = getUpdatedHexagonsSvg(grid, xIndex, i, i + 1);
                    unchecked {
                        ++j;
                    }
                }

                unchecked {
                    ++i;
                }
            }
        } else if (honeycomb.baseHexagon.hexagonType == uint8(HEXAGON_TYPE.FLAT)) {
            uint8 flatTopRows = grid.rows * 2 - 1;
            grid.totalGradients = flatTopRows;
            uint8 halfRows = grid.rows / 2;

            for (uint8 i; i < flatTopRows; ) {
                // Determine hexagons in row.
                uint8 hexagonsInRow;
                if (i <= grid.rows / 2) {
                    // ascending, i.e. rows = 1 2 3 4 5 when rows = 5
                    hexagonsInRow = i + 1;
                } else if (i < flatTopRows - halfRows - 1) {
                    // alternate between rows / 2 + 1 and rows / 2 every other row
                    hexagonsInRow = (halfRows + i) % 2 == 0 ? halfRows + 1 : halfRows;
                } else {
                    // descending, i.e. rows = 5, 4, 3, 2, 1 when rows = 5
                    hexagonsInRow = flatTopRows - i;
                }

                // Assign indexes for each hexagon.
                for (uint8 j; j < hexagonsInRow; ) {
                    uint8 xIndex = (grid.rows - hexagonsInRow) - halfRows + (j * 2);
                    grid.hexagonsSvg = getUpdatedHexagonsSvg(grid, xIndex, i, i + 1);
                    unchecked {
                        ++j;
                    }
                }

                unchecked {
                    ++i;
                }
            }
        }

        return grid;
    }

    /// @dev Get the honeycomb grid for a diamond shape.
    /// @param honeycomb The honeycomb data used for rendering.
    function getDiamondGrid(IHoneycombs.Honeycomb memory honeycomb) public pure returns (IHoneycombs.Grid memory) {
        IHoneycombs.Grid memory grid;

        // Get random rows from 3 to honeycomb.canvas.maxHexagonsPerLine, only odd.
        grid.rows = uint8(
            Utilities.random(honeycomb.seed, "rows", (honeycomb.canvas.maxHexagonsPerLine / 2) - 1) * 2 + 3
        );

        // Determine positioning of entire grid, which is based on the longest row.
        grid.longestRowCount = grid.rows / 2 + 1;
        grid = addGridPositioning(honeycomb, grid); // appends to grid object

        // Create diamond grid. Both flat top and pointy top result in the same grid, so no need to check hexagon type.
        for (uint8 i; i < grid.rows; ) {
            // Determine hexagons in row. Pattern is ascending/descending sequence, i.e 1 2 3 2 1 when rows = 5.
            uint8 hexagonsInRow = i < grid.rows / 2 ? i + 1 : grid.rows - i;
            uint8 firstXInRow = i < grid.rows / 2 ? grid.rows / 2 - i : i - grid.rows / 2;

            // Assign indexes for each hexagon.
            for (uint8 j; j < hexagonsInRow; ) {
                uint8 xIndex = firstXInRow + (j * 2);
                grid.hexagonsSvg = getUpdatedHexagonsSvg(grid, xIndex, i, i + 1);
                unchecked {
                    ++j;
                }
            }

            unchecked {
                ++i;
            }
        }

        grid.totalGradients = grid.rows;
        return grid;
    }

    /// @dev Get the honeycomb grid for a triangle shape.
    /// @param honeycomb The honeycomb data used for rendering.
    function getTriangleGrid(IHoneycombs.Honeycomb memory honeycomb) public pure returns (IHoneycombs.Grid memory) {
        IHoneycombs.Grid memory grid;

        // Get random rows from 2 to honeycomb.canvas.maxHexagonsPerLine.
        grid.rows = uint8(Utilities.random(honeycomb.seed, "rows", honeycomb.canvas.maxHexagonsPerLine - 1) + 2);

        // Determine positioning of entire grid, which is based on the longest row.
        grid.longestRowCount = grid.rows;
        grid = addGridPositioning(honeycomb, grid); // appends to grid object

        // Create grid based on hexagon base type.
        if (honeycomb.baseHexagon.hexagonType == uint8(HEXAGON_TYPE.POINTY)) {
            grid.totalGradients = grid.rows;

            // Iterate through rows - will only be north/south facing (design).
            for (uint8 i; i < grid.rows; ) {
                // Assign indexes for each hexagon. Each row has i + 1 hexagons.
                for (uint8 j; j < i + 1; ) {
                    uint8 xIndex = grid.rows - 1 - i + (j * 2);
                    grid.hexagonsSvg = getUpdatedHexagonsSvg(grid, xIndex, i, i + 1);
                    unchecked {
                        ++j;
                    }
                }

                unchecked {
                    ++i;
                }
            }
        } else if (honeycomb.baseHexagon.hexagonType == uint8(HEXAGON_TYPE.FLAT)) {
            uint8 flatTopRows = grid.rows * 2 - 1;
            grid.totalGradients = flatTopRows;

            // Iterate through rows - will only be west/east facing (design).
            for (uint8 i; i < flatTopRows; ) {
                // Determine hexagons in row. First half is ascending. Second half is descending.
                uint8 hexagonsInRow;
                if (i <= flatTopRows / 2) {
                    // ascending with peak, i.e. rows = 1 1 2 2 3 when rows = 5
                    hexagonsInRow = i / 2 + 1;
                } else {
                    // descending with peak, i.e. rows = 2 2 1 1 when rows = 5
                    hexagonsInRow = ((flatTopRows - i - 1) / 2) + 1;
                }

                // Assign indexes for each hexagon. Each row has i + 1 hexagons.
                for (uint8 j; j < hexagonsInRow; ) {
                    uint8 xIndex = (i % 2) + (j * 2);
                    grid.hexagonsSvg = getUpdatedHexagonsSvg(grid, xIndex, i, i + 1);
                    unchecked {
                        ++j;
                    }
                }

                unchecked {
                    ++i;
                }
            }
        }

        return grid;
    }

    /// @dev Generate the overall honeycomb grid, including the final svg.
    /// @dev Using double coordinates: https://www.redblobgames.com/grids/hexagons/#coordinates-doubled
    /// @param honeycomb The honeycomb data used for rendering.
    /// @return (bytes, uint8, uint8) The svg, totalGradients, and rows.
    function generateGrid(IHoneycombs.Honeycomb memory honeycomb) public pure returns (bytes memory, uint8, uint8) {
        // Partial grid object used to store supportive variables
        IHoneycombs.Grid memory gridData;

        // Get grid data based on shape.
        if (honeycomb.grid.shape == uint8(SHAPE.TRIANGLE)) {
            gridData = getTriangleGrid(honeycomb);
        } else if (honeycomb.grid.shape == uint8(SHAPE.DIAMOND)) {
            gridData = getDiamondGrid(honeycomb);
        } else if (honeycomb.grid.shape == uint8(SHAPE.HEXAGON)) {
            gridData = getHexagonGrid(honeycomb);
        } else if (honeycomb.grid.shape == uint8(SHAPE.RANDOM)) {
            gridData = getRandomGrid(honeycomb);
        }

        // Generate grid svg.
        // prettier-ignore
        bytes memory svg = abi.encodePacked(
            '<g transform="scale(1) rotate(', 
                    Utilities.uint2str(honeycomb.grid.rotation) ,',', 
                    Utilities.uint2str(honeycomb.canvas.size / 2) ,',', 
                    Utilities.uint2str(honeycomb.canvas.size / 2), ')">',
                gridData.hexagonsSvg,
            '</g>'
        );

        return (svg, gridData.totalGradients, gridData.rows);
    }
}
// File: contracts/libraries/HoneycombsArt.sol



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

import "../interfaces/IHoneycombs.sol";
import "./Utilities.sol";
import "./GridArt.sol";
import "./GradientsArt.sol";

/**
@title  HoneycombsArt
@notice Renders the Honeycombs visuals.
*/
library HoneycombsArt {
    enum HEXAGON_TYPE { FLAT, POINTY } // prettier-ignore
    enum SHAPE { TRIANGLE, DIAMOND, HEXAGON, RANDOM } // prettier-ignore

    /// @dev Generate relevant rendering data by loading honeycomb from storage and filling its attribute settings.
    /// @param honeycombs The DB containing all honeycombs.
    /// @param tokenId The tokenId of the honeycomb to render.
    function generateHoneycombRenderData(
        IHoneycombs.Honeycombs storage honeycombs,
        uint256 tokenId
    ) public view returns (IHoneycombs.Honeycomb memory honeycomb) {
        IHoneycombs.StoredHoneycomb memory stored = honeycombs.all[tokenId];
        honeycomb.stored = stored;

        // Determine if the honeycomb is revealed via the epoch randomness.
        uint128 randomness = honeycombs.epochs[stored.epoch].randomness;
        honeycomb.isRevealed = randomness > 0;

        // Exit early if the honeycomb is not revealed.
        if (!honeycomb.isRevealed) {
            return honeycomb;
        }

        // Set the seed.
        honeycomb.seed = (uint256(keccak256(abi.encodePacked(randomness, stored.seed))) % type(uint128).max);

        // Set the canvas properties.
        honeycomb.canvas.color = Utilities.random(honeycomb.seed, "canvasColor", 2) == 0 ? "White" : "Black";
        honeycomb.canvas.size = 810;
        honeycomb.canvas.hexagonSize = 72;
        honeycomb.canvas.maxHexagonsPerLine = 8; // (810 (canvasSize) - 90 (padding) / 72 (hexagon size)) - 1 = 8

        // Get the base hexagon properties.
        honeycomb.baseHexagon.hexagonType = uint8(
            Utilities.random(honeycomb.seed, "hexagonType", 2) == 0 ? HEXAGON_TYPE.FLAT : HEXAGON_TYPE.POINTY
        );
        honeycomb.baseHexagon.path = GridArt.getHexagonPath(honeycomb.baseHexagon.hexagonType);
        honeycomb.baseHexagon.strokeWidth = uint8(Utilities.random(honeycomb.seed, "strokeWidth", 15) + 3);
        honeycomb.baseHexagon.fillColor = Utilities.random(honeycomb.seed, "hexagonFillColor", 2) == 0
            ? "White"
            : "Black";

        /**
         * Get the grid properties, including the actual svg.
         * Note: Random shapes must only have pointy top hexagon bases (artist design choice).
         * Note: Triangles have unique rotation options (artist design choice).
         */
        honeycomb.grid.shape = uint8(Utilities.random(honeycomb.seed, "gridShape", 4));
        if (honeycomb.grid.shape == uint8(SHAPE.RANDOM)) {
            honeycomb.baseHexagon.hexagonType = uint8(HEXAGON_TYPE.POINTY);
            honeycomb.baseHexagon.path = GridArt.getHexagonPath(honeycomb.baseHexagon.hexagonType);
        }

        honeycomb.grid.rotation = honeycomb.grid.shape == uint8(SHAPE.TRIANGLE)
            ? uint16(Utilities.random(honeycomb.seed, "rotation", 4) * 90)
            : uint16(Utilities.random(honeycomb.seed, "rotation", 12) * 30);

        (honeycomb.grid.svg, honeycomb.grid.totalGradients, honeycomb.grid.rows) = GridArt.generateGrid(honeycomb);

        // Get the gradients properties, including the actual svg.
        honeycomb.gradients.chrome = GradientsArt.getChrome(uint8(Utilities.random(honeycomb.seed, "chrome", 7)));
        honeycomb.gradients.duration = GradientsArt.getDuration(uint16(Utilities.random(honeycomb.seed, "duration", 4)));
        honeycomb.gradients.direction = uint8(Utilities.random(honeycomb.seed, "direction", 2));
        honeycomb.gradients.svg = GradientsArt.generateGradientsSvg(honeycomb);
    }

    /// @dev Generate the complete SVG and its associated data for a honeycomb.
    /// @param honeycombs The DB containing all honeycombs.
    /// @param tokenId The tokenId of the honeycomb to render.
    function generateHoneycomb(
        IHoneycombs.Honeycombs storage honeycombs,
        uint256 tokenId
    ) public view returns (IHoneycombs.Honeycomb memory) {
        IHoneycombs.Honeycomb memory honeycomb = generateHoneycombRenderData(honeycombs, tokenId);

        if (!honeycomb.isRevealed) {
            // prettier-ignore
            honeycomb.svg = abi.encodePacked(
                '<svg viewBox="0 0 810 810" fill="#ffffff" xmlns="http://www.w3.org/2000/svg" style="width:100%;background:black;">',
                    '<rect width="810" height="810" fill="black" transform="translate(-37.98, -37.98)" />',
                    '<g id="logo" transform="translate(200, 200)">',
                        '<g id="hexagon" transform="translate(17.089965000000007,17.089965000000007), scale(0.91)">',
                            '<path transform="translate(-37.98, -37.98), scale(28.48375)" fill="#181818"',
                                'd="M9.166.33a2.25 2.25 0 00-2.332 0l-5.25 3.182A2.25 2.25 0 00.5 5.436v5.128a2.25 2.25 0 001.084 1.924l5.25 3.182a2.25 2.25 0 002.332 0l5.25-3.182a2.25 2.25 0 001.084-1.924V5.436a2.25 2.25 0 00-1.084-1.924L9.166.33z">',
                            '</path>',
                        '</g>'
                        '<g id="hummingbird" stroke-linecap="round" stroke-linejoin="round" stroke="#ffc107" stroke-width="6">',
                            '<path d="M366.314,97.749c-0.129-1.144-1.544-1.144-2.389-1.144c-6.758,0-37.499,4.942-62.82,13.081 c-1.638,0.527-2.923,0.783-3.928,0.783c-1.961,0-2.722-0.928-4.254-3.029c-1.848-2.533-4.379-6.001-11.174-8.914 c-2.804-1.202-6.057-1.812-9.667-1.812c-14.221,0-32.199,9.312-42.749,22.142c-0.066,0.08-0.103,0.096-0.107,0.096 c-0.913,0-4.089-3.564-9.577-17.062c-4.013-9.87-8.136-22.368-10.504-31.842c-3.553-14.212-13.878-34.195-20.71-47.417 c-2.915-5.642-5.218-10.098-5.797-11.836c-0.447-1.339-1.15-2.019-2.091-2.019c-0.604,0-1.184,0.3-1.773,0.917 c-6.658,6.983-20.269,65.253-19.417,83.132c0.699,14.682,12.291,24.61,17.861,29.381c0.659,0.564,1.363,1.167,1.911,1.67 c-2.964-1.06-9.171-6.137-17.406-12.873c-11.881-9.718-29.836-24.403-54.152-40.453c-34.064-22.484-55.885-44.77-68.922-58.084 C29.964,3.599,26.338,0,23.791,0c-0.605,0-1.707,0.227-2.278,1.75c-2.924,7.798,0.754,88.419,37.074,132.002 c20.279,24.335,46.136,36.829,63.246,45.097c9.859,4.764,17.647,8.527,18.851,12.058c0.273,0.803,0.203,1.573-0.223,2.425 c-1.619,3.238-4.439,7.193-8.011,12.202c-9.829,13.783-24.682,34.613-35.555,69.335c-4.886,15.601-55.963,70.253-69.247,83.537 c-0.648,0.648-15.847,15.917-14.06,20.229c0.142,0.344,0.613,1.143,1.908,1.143c3.176,0,11.554-5.442,24.902-16.195 c17.47-14.073,29.399-25.848,38.11-34.452c8.477-8.374,13.784-13.596,17.427-14.161c-0.333,1.784-1.385,6.367-4.576,17.926 c-0.077,0.279-0.238,0.938,0.127,1.418l0.355,0.576h0.495c0.001,0,0.002,0,0.003,0c0.773,0,1.172-0.618,4.53-4.786 c10.244-12.714,41.417-51.561,84.722-60.067c25.376-4.985,56.886-28.519,68.008-63.854c16.822-53.439,30.902-87.056,105.176-104.081 C366.502,99.413,366.428,98.751,366.314,97.749z" />'
                        '</g>',
                    '</g>',
                '</svg>'
            );
        } else {
            // prettier-ignore
            honeycomb.svg = abi.encodePacked(
                // Note: Use 810 as hardcoded size to avoid stack too deep error.
                '<svg viewBox="0 0 810 810" fill="none" xmlns="http://www.w3.org/2000/svg"', 
                        'style="width:100%;background:', honeycomb.canvas.color, ';">',
                    '<defs>',
                        '<path id="hexagon" fill="', honeycomb.baseHexagon.fillColor,
                            '" stroke-width="', Utilities.uint2str(honeycomb.baseHexagon.strokeWidth),
                            '" d="', honeycomb.baseHexagon.path ,'" />',
                        honeycomb.gradients.svg,
                    '</defs>',
                    '<rect width="810" height="810" fill="', honeycomb.canvas.color, '"/>',
                    honeycomb.grid.svg,
                    '<rect width="810" height="810" fill="transparent">',
                        '<animate attributeName="width" from="810" to="0" dur="0.2s" fill="freeze" ',
                            'begin="click" id="animation"/>',
                    '</rect>',
                '</svg>'
            );
        }

        return honeycomb;
    }
}
// File: contracts/libraries/HoneycombsMetadata.sol



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

import "@openzeppelin/contracts/utils/Base64.sol";

import "./HoneycombsArt.sol";
import "../interfaces/IHoneycombs.sol";
import "./Utilities.sol";

/**
@title  HoneycombsMetadata
@notice Renders ERC721 compatible metadata for Honeycombs.
*/
library HoneycombsMetadata {
    /// @dev Render the JSON Metadata for a given Honeycombs token.
    /// @param honeycombs The DB containing all honeycombs.
    /// @param tokenId The id of the token to render.
    function tokenURI(IHoneycombs.Honeycombs storage honeycombs, uint256 tokenId) public view returns (string memory) {
        IHoneycombs.Honeycomb memory honeycomb = HoneycombsArt.generateHoneycomb(honeycombs, tokenId);

        // prettier-ignore
        bytes memory metadata = abi.encodePacked(
            '{',
                '"name": "Honeycomb #', Utilities.uint2str(tokenId), '",',
                '"description": "You are searching the world for treasure, but the real treasure is yourself. - Rumi",',
                '"image": ',
                    '"data:image/svg+xml;base64,',
                    Base64.encode(honeycomb.svg),
                    '",',
                '"animation_url": ',
                    '"data:text/html;base64,',
                    Base64.encode(generateHTML(tokenId, honeycomb.svg)),
                    '",',
                '"attributes": [', attributes(honeycomb), ']',
            '}'
        );

        return string(abi.encodePacked("data:application/json;base64,", Base64.encode(metadata)));
    }

    /// @dev Render the JSON atributes for a given Honeycombs token.
    /// @param honeycomb The honeycomb to render.
    function attributes(IHoneycombs.Honeycomb memory honeycomb) public pure returns (bytes memory) {
        return
            abi.encodePacked(
                honeycomb.isRevealed ? trait("Canvas Color", honeycomb.canvas.color, ",") : "",
                honeycomb.isRevealed
                    ? trait("Base Hexagon", honeycomb.baseHexagon.hexagonType == 0 ? "Flat Top" : "Pointy Top", ",")
                    : "",
                honeycomb.isRevealed ? trait("Base Hexagon Fill Color", honeycomb.baseHexagon.fillColor, ",") : "",
                honeycomb.isRevealed
                    ? trait("Stroke Width", Utilities.uint2str(honeycomb.baseHexagon.strokeWidth), ",")
                    : "",
                honeycomb.isRevealed ? trait("Shape", shapes(honeycomb.grid.shape), ",") : "",
                honeycomb.isRevealed ? trait("Rows", Utilities.uint2str(honeycomb.grid.rows), ",") : "",
                honeycomb.isRevealed ? trait("Rotation", Utilities.uint2str(honeycomb.grid.rotation), ",") : "",
                honeycomb.isRevealed ? trait("Chrome", chromes(honeycomb.gradients.chrome), ",") : "",
                honeycomb.isRevealed ? trait("Duration", durations(honeycomb.gradients.duration), ",") : "",
                honeycomb.isRevealed
                    ? trait("Direction", honeycomb.gradients.direction == 0 ? "Forward" : "Reverse", ",")
                    : "",
                honeycomb.isRevealed == false ? trait("Revealed", "No", ",") : "",
                trait("Day", Utilities.uint2str(honeycomb.stored.day), "")
            );
    }

    /// @dev Get the names for different shapes. Compare HoneycombsArt.getShape().
    /// @param shapeIndex The index of the shape.
    function shapes(uint8 shapeIndex) public pure returns (string memory) {
        return ["Triangle", "Diamond", "Hexagon", "Random"][shapeIndex];
    }

    /// @dev Get the names for different chromes (max colors). Compare HoneycombsArt.getChrome().
    /// @param chrome The chrome of the gradient, which is the number of max colors.
    function chromes(uint8 chrome) public pure returns (string memory) {
        if (chrome <= 6) {
            return ["Monochrome", "Dichrome", "Trichrome", "Tetrachrome", "Pentachrome", "Hexachrome"][chrome - 1];
        } else {
            return "Many";
        }
    }

    /// @dev Get the names for different durations. Compare HoneycombsArt.getDuration().
    /// @param duration The duration in seconds.
    function durations(uint16 duration) public pure returns (string memory) {
        if (duration == 10) {
            return "Rave";
        } else if (duration == 40) {
            return "Normal";
        } else if (duration == 80) {
            return "Soothing";
        } else {
            return "Meditative";
        }
    }

    /// @dev Generate the SVG snipped for a single attribute.
    /// @param traitType The `trait_type` for this trait.
    /// @param traitValue The `value` for this trait.
    /// @param append Helper to append a comma.
    function trait(
        string memory traitType,
        string memory traitValue,
        string memory append
    ) public pure returns (string memory) {
        // prettier-ignore
        return string(abi.encodePacked(
            '{',
                '"trait_type": "', traitType, '",'
                '"value": "', traitValue, '"'
            '}',
            append
        ));
    }

    /// @dev Generate the HTML for the animation_url in the metadata.
    /// @param tokenId The id of the token to generate the embed for.
    /// @param svg The rendered SVG code to embed in the HTML.
    function generateHTML(uint256 tokenId, bytes memory svg) public pure returns (bytes memory) {
        // prettier-ignore
        return abi.encodePacked(
            '<!DOCTYPE html>',
            '<html lang="en">',
            '<head>',
                '<meta charset="UTF-8">',
                '<meta http-equiv="X-UA-Compatible" content="IE=edge">',
                '<meta name="viewport" content="width=device-width, initial-scale=1.0">',
                '<title>Honeycomb #', Utilities.uint2str(tokenId), '</title>',
                '<style>',
                    'html,',
                    'body {',
                        'margin: 0;',
                        'background: #EFEFEF;',
                        'overflow: hidden;',
                    '}',
                    'svg {',
                        'max-width: 100vw;',
                        'max-height: 100vh;',
                    '}',
                '</style>',
            '</head>',
            '<body>',
                svg,
            '</body>',
            '</html>'
        );
    }
}

// File: contracts/libraries/Utilities.sol



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

library Utilities {
    /// @dev Zero-index based pseudorandom number based on one input and max bound
    function random(uint256 input, uint256 _max) internal pure returns (uint256) {
        return (uint256(keccak256(abi.encodePacked(input))) % _max);
    }

    /// @dev Zero-index based salted pseudorandom number based on two inputs and max bound
    function random(uint256 input, bytes memory salt, uint256 _max) internal pure returns (uint256) {
        return (uint256(keccak256(abi.encodePacked(input, salt))) % _max);
    }

    /// @dev Convert an integer to a string
    function uint2str(uint256 _i) internal pure returns (string memory _uintAsString) {
        if (_i == 0) {
            return "0";
        }
        uint256 j = _i;
        uint256 len;
        while (j != 0) {
            ++len;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint256 k = len;
        while (_i != 0) {
            k = k - 1;
            uint8 temp = (48 + uint8(_i - (_i / 10) * 10));
            bytes1 b1 = bytes1(temp);
            bstr[k] = b1;
            _i /= 10;
        }
        return string(bstr);
    }

    /// @dev Get the larger number
    function max(uint8 one, uint8 two) internal pure returns (uint8) {
        return one > two ? one : two;
    }

    /// @dev Get the absolute difference between two numbers
    function absDiff(uint8 one, uint8 two) internal pure returns (uint8) {
        return one > two ? one - two : two - one;
    }

    /// @dev Swap two numbers
    function swap(uint16 one, uint16 two) internal pure returns (uint16, uint16) {
        return (two, one);
    }

    /// @dev Get the days since another date (input is seconds)
    function day(uint256 from, uint256 to) internal pure returns (uint24) {
        return uint24((to - from) / 24 hours + 1);
    }
}

// File: contracts/standards/HONEYCOMBS721.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0)

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "../interfaces/IERC4906.sol";

/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is available separately as
 * {ERC721Enumerable}.
 */
contract HONEYCOMBS721 is Context, ERC165, IERC721, IERC721Metadata, IERC4906 {
    error ERC721__InvalidApproval();
    error ERC721__InvalidOwner();
    error ERC721__InvalidToken();
    error ERC721__NotAllowed();
    error ERC721__TokenExists();
    error ERC721__TransferToNonReceiver();
    error ERC721__TransferToZero();

    using Address for address;
    using Strings for uint256;

    // Token name
    string private _name;

    // Token symbol
    string private _symbol;

    // Mapping from token ID to owner address
    mapping(uint256 => address) private _owners;

    // Mapping owner address to token count
    mapping(address => uint256) private _balances;

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

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

    /**
     * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
     */
    constructor() {
        _name = "Honeycombs";
        _symbol = "HONEYCOMBS";
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address owner) public view virtual override returns (uint256) {
        if (owner == address(0)) {
            revert ERC721__InvalidOwner();
        }
        return _balances[owner];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        address owner = _ownerOf(tokenId);
        if (owner == address(0)) {
            revert ERC721__InvalidToken();
        }
        return owner;
    }

    /**
     * @dev See {IERC721Metadata-name}.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        _requireMinted(tokenId);

        return "";
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public virtual override {
        address owner = HONEYCOMBS721.ownerOf(tokenId);

        if (to == owner || (_msgSender() != owner && !isApprovedForAll(owner, _msgSender()))) {
            revert ERC721__InvalidApproval();
        }

        _approve(to, tokenId);
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        _requireMinted(tokenId);

        return _tokenApprovals[tokenId];
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(address from, address to, uint256 tokenId) public virtual override {
        if (!_isApprovedOrOwner(_msgSender(), tokenId)) {
            revert ERC721__NotAllowed();
        }

        _transfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
        if (!_isApprovedOrOwner(_msgSender(), tokenId)) {
            revert ERC721__NotAllowed();
        }
        _safeTransfer(from, to, tokenId, data);
    }

    /**
     * @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.
     *
     * `data` is additional data, it has no specified format and it is sent in call to `to`.
     *
     * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
        _transfer(from, to, tokenId);
        if (!_checkOnERC721Received(from, to, tokenId, data)) {
            revert ERC721__TransferToNonReceiver();
        }
    }

    /**
     * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
     */
    function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
        return _owners[tokenId];
    }

    /**
     * @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 (`_mint`),
     * and stop existing when they are burned (`_burn`).
     */
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _ownerOf(tokenId) != address(0);
    }

    /**
     * @dev Returns whether `spender` is allowed to manage `tokenId`.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        address owner = HONEYCOMBS721.ownerOf(tokenId);
        return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
    }

    /**
     * @dev Safely mints `tokenId` and transfers it to `to`.
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
        _mint(to, tokenId);
        if (!_checkOnERC721Received(address(0), to, tokenId, data)) {
            revert ERC721__TransferToNonReceiver();
        }
    }

    /**
     * @dev Safely mints `tokenId` and transfers it to `to` after an inital transfer to `via`.
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeMintVia(address to, address via, uint256 tokenId) internal virtual {
        _safeMintVia(to, via, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeMintVia(address to, address via, uint256 tokenId, bytes memory data) internal virtual {
        _mintVia(to, via, tokenId);
        if (!_checkOnERC721Received(address(0), to, tokenId, data)) {
            revert ERC721__TransferToNonReceiver();
        }
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _mint(address to, uint256 tokenId) internal virtual {
        _mintState(to, tokenId);

        emit Transfer(address(0), to, tokenId);

        _afterTokenTransfer(address(0), to, tokenId, 1);
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to` after a transfer to `via`
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _mintVia(address to, address via, uint256 tokenId) internal virtual {
        _mintState(to, tokenId);

        emit Transfer(address(0), via, tokenId);
        emit Transfer(via, to, tokenId);

        _afterTokenTransfer(address(0), to, tokenId, 1);
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     */
    function _mintState(address to, uint256 tokenId) internal virtual {
        if (to == address(0)) {
            revert ERC721__TransferToZero();
        }
        if (_exists(tokenId)) {
            revert ERC721__TokenExists();
        }

        _beforeTokenTransfer(address(0), to, tokenId, 1);

        // Check that tokenId was not minted by `_beforeTokenTransfer` hook
        if (_exists(tokenId)) {
            revert ERC721__TokenExists();
        }

        unchecked {
            // Will not overflow unless all 2**256 token ids are minted to the same owner.
            // Given that tokens are minted one by one, it is impossible in practice that
            // this ever happens. Might change if we allow batch minting.
            // The ERC fails to describe this case.
            _balances[to] += 1;
        }

        _owners[tokenId] = to;
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     * This is an internal function that does not check if the sender is authorized to operate on the token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal virtual {
        address owner = HONEYCOMBS721.ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId, 1);

        // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
        owner = HONEYCOMBS721.ownerOf(tokenId);

        // Clear approvals
        delete _tokenApprovals[tokenId];

        unchecked {
            // Cannot overflow, as that would require more tokens to be burned/transferred
            // out than the owner initially received through minting and transferring in.
            _balances[owner] -= 1;
        }
        delete _owners[tokenId];

        emit Transfer(owner, address(0), tokenId);

        _afterTokenTransfer(owner, address(0), tokenId, 1);
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(address from, address to, uint256 tokenId) internal virtual {
        if (HONEYCOMBS721.ownerOf(tokenId) != from) {
            revert ERC721__InvalidOwner();
        }
        if (to == address(0)) {
            revert ERC721__TransferToZero();
        }

        _beforeTokenTransfer(from, to, tokenId, 1);

        // Check that tokenId was not transferred by `_beforeTokenTransfer` hook
        if (HONEYCOMBS721.ownerOf(tokenId) != from) {
            revert ERC721__InvalidOwner();
        }

        // Clear approvals from the previous owner
        delete _tokenApprovals[tokenId];

        unchecked {
            // `_balances[from]` cannot overflow for the same reason as described in `_burn`:
            // `from`'s balance is the number of token held, which is at least one before the current
            // transfer.
            // `_balances[to]` could overflow in the conditions described in `_mint`. That would require
            // all 2**256 token ids to be minted, which in practice is impossible.
            _balances[from] -= 1;
            _balances[to] += 1;
        }
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

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

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * Emits an {Approval} event.
     */
    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(HONEYCOMBS721.ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits an {ApprovalForAll} event.
     */
    function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
        if (owner == operator) {
            revert ERC721__InvalidApproval();
        }
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Reverts if the `tokenId` has not been minted yet.
     */
    function _requireMinted(uint256 tokenId) internal view virtual {
        if (!_exists(tokenId)) {
            revert ERC721__InvalidToken();
        }
    }

    /**
     * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
     * The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param data bytes optional data to send along with the call
     * @return bool whether the call correctly returned the expected magic value
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert ERC721__TransferToNonReceiver();
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
     * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
     * - When `from` is zero, the tokens will be minted for `to`.
     * - When `to` is zero, ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     * - `batchSize` is non-zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 /* firstTokenId */,
        uint256 batchSize
    ) internal virtual {
        if (batchSize > 1) {
            if (from != address(0)) {
                _balances[from] -= batchSize;
            }
            if (to != address(0)) {
                _balances[to] += batchSize;
            }
        }
    }

    /**
     * @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
     * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
     * - When `from` is zero, the tokens were minted for `to`.
     * - When `to` is zero, ``from``'s tokens were burned.
     * - `from` and `to` are never both zero.
     * - `batchSize` is non-zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
}