creation_sourcecode: {{
    "language": "Solidity",
    "sources": {
      "@openzeppelin-5.0/contracts/access/Ownable.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n    address private _owner;\n\n    /**\n     * @dev The caller account is not authorized to perform an operation.\n     */\n    error OwnableUnauthorizedAccount(address account);\n\n    /**\n     * @dev The owner is not a valid owner account. (eg. `address(0)`)\n     */\n    error OwnableInvalidOwner(address owner);\n\n    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n     */\n    constructor(address initialOwner) {\n        if (initialOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(initialOwner);\n    }\n\n    /**\n     * @dev Throws if called by any account other than the owner.\n     */\n    modifier onlyOwner() {\n        _checkOwner();\n        _;\n    }\n\n    /**\n     * @dev Returns the address of the current owner.\n     */\n    function owner() public view virtual returns (address) {\n        return _owner;\n    }\n\n    /**\n     * @dev Throws if the sender is not the owner.\n     */\n    function _checkOwner() internal view virtual {\n        if (owner() != _msgSender()) {\n            revert OwnableUnauthorizedAccount(_msgSender());\n        }\n    }\n\n    /**\n     * @dev Leaves the contract without owner. It will not be possible to call\n     * `onlyOwner` functions. Can only be called by the current owner.\n     *\n     * NOTE: Renouncing ownership will leave the contract without an owner,\n     * thereby disabling any functionality that is only available to the owner.\n     */\n    function renounceOwnership() public virtual onlyOwner {\n        _transferOwnership(address(0));\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Can only be called by the current owner.\n     */\n    function transferOwnership(address newOwner) public virtual onlyOwner {\n        if (newOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual {\n        address oldOwner = _owner;\n        _owner = newOwner;\n        emit OwnershipTransferred(oldOwner, newOwner);\n    }\n}\n"
      },
      "@openzeppelin-5.0/contracts/interfaces/draft-IERC6093.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)\npragma solidity ^0.8.20;\n\n/**\n * @dev Standard ERC20 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.\n */\ninterface IERC20Errors {\n    /**\n     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n     * @param sender Address whose tokens are being transferred.\n     * @param balance Current balance for the interacting account.\n     * @param needed Minimum amount required to perform a transfer.\n     */\n    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\n\n    /**\n     * @dev Indicates a failure with the token `sender`. Used in transfers.\n     * @param sender Address whose tokens are being transferred.\n     */\n    error ERC20InvalidSender(address sender);\n\n    /**\n     * @dev Indicates a failure with the token `receiver`. Used in transfers.\n     * @param receiver Address to which tokens are being transferred.\n     */\n    error ERC20InvalidReceiver(address receiver);\n\n    /**\n     * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.\n     * @param spender Address that may be allowed to operate on tokens without being their owner.\n     * @param allowance Amount of tokens a `spender` is allowed to operate with.\n     * @param needed Minimum amount required to perform a transfer.\n     */\n    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\n\n    /**\n     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n     * @param approver Address initiating an approval operation.\n     */\n    error ERC20InvalidApprover(address approver);\n\n    /**\n     * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\n     * @param spender Address that may be allowed to operate on tokens without being their owner.\n     */\n    error ERC20InvalidSpender(address spender);\n}\n\n/**\n * @dev Standard ERC721 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.\n */\ninterface IERC721Errors {\n    /**\n     * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.\n     * Used in balance queries.\n     * @param owner Address of the current owner of a token.\n     */\n    error ERC721InvalidOwner(address owner);\n\n    /**\n     * @dev Indicates a `tokenId` whose `owner` is the zero address.\n     * @param tokenId Identifier number of a token.\n     */\n    error ERC721NonexistentToken(uint256 tokenId);\n\n    /**\n     * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\n     * @param sender Address whose tokens are being transferred.\n     * @param tokenId Identifier number of a token.\n     * @param owner Address of the current owner of a token.\n     */\n    error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\n\n    /**\n     * @dev Indicates a failure with the token `sender`. Used in transfers.\n     * @param sender Address whose tokens are being transferred.\n     */\n    error ERC721InvalidSender(address sender);\n\n    /**\n     * @dev Indicates a failure with the token `receiver`. Used in transfers.\n     * @param receiver Address to which tokens are being transferred.\n     */\n    error ERC721InvalidReceiver(address receiver);\n\n    /**\n     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n     * @param operator Address that may be allowed to operate on tokens without being their owner.\n     * @param tokenId Identifier number of a token.\n     */\n    error ERC721InsufficientApproval(address operator, uint256 tokenId);\n\n    /**\n     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n     * @param approver Address initiating an approval operation.\n     */\n    error ERC721InvalidApprover(address approver);\n\n    /**\n     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n     * @param operator Address that may be allowed to operate on tokens without being their owner.\n     */\n    error ERC721InvalidOperator(address operator);\n}\n\n/**\n * @dev Standard ERC1155 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.\n */\ninterface IERC1155Errors {\n    /**\n     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n     * @param sender Address whose tokens are being transferred.\n     * @param balance Current balance for the interacting account.\n     * @param needed Minimum amount required to perform a transfer.\n     * @param tokenId Identifier number of a token.\n     */\n    error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\n\n    /**\n     * @dev Indicates a failure with the token `sender`. Used in transfers.\n     * @param sender Address whose tokens are being transferred.\n     */\n    error ERC1155InvalidSender(address sender);\n\n    /**\n     * @dev Indicates a failure with the token `receiver`. Used in transfers.\n     * @param receiver Address to which tokens are being transferred.\n     */\n    error ERC1155InvalidReceiver(address receiver);\n\n    /**\n     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n     * @param operator Address that may be allowed to operate on tokens without being their owner.\n     * @param owner Address of the current owner of a token.\n     */\n    error ERC1155MissingApprovalForAll(address operator, address owner);\n\n    /**\n     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n     * @param approver Address initiating an approval operation.\n     */\n    error ERC1155InvalidApprover(address approver);\n\n    /**\n     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n     * @param operator Address that may be allowed to operate on tokens without being their owner.\n     */\n    error ERC1155InvalidOperator(address operator);\n\n    /**\n     * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\n     * Used in batch transfers.\n     * @param idsLength Length of the array of token identifiers\n     * @param valuesLength Length of the array of token amounts\n     */\n    error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\n}\n"
      },
      "@openzeppelin-5.0/contracts/interfaces/IERC2981.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC2981.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"../utils/introspection/IERC165.sol\";\n\n/**\n * @dev Interface for the NFT Royalty Standard.\n *\n * A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal\n * support for royalty payments across all NFT marketplaces and ecosystem participants.\n */\ninterface IERC2981 is IERC165 {\n    /**\n     * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of\n     * exchange. The royalty amount is denominated and should be paid in that same unit of exchange.\n     */\n    function royaltyInfo(\n        uint256 tokenId,\n        uint256 salePrice\n    ) external view returns (address receiver, uint256 royaltyAmount);\n}\n"
      },
      "@openzeppelin-5.0/contracts/token/ERC721/extensions/IERC721Metadata.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Metadata.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC721} from \"../IERC721.sol\";\n\n/**\n * @title ERC-721 Non-Fungible Token Standard, optional metadata extension\n * @dev See https://eips.ethereum.org/EIPS/eip-721\n */\ninterface IERC721Metadata is IERC721 {\n    /**\n     * @dev Returns the token collection name.\n     */\n    function name() external view returns (string memory);\n\n    /**\n     * @dev Returns the token collection symbol.\n     */\n    function symbol() external view returns (string memory);\n\n    /**\n     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.\n     */\n    function tokenURI(uint256 tokenId) external view returns (string memory);\n}\n"
      },
      "@openzeppelin-5.0/contracts/token/ERC721/IERC721.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"../../utils/introspection/IERC165.sol\";\n\n/**\n * @dev Required interface of an ERC721 compliant contract.\n */\ninterface IERC721 is IERC165 {\n    /**\n     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\n     */\n    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n\n    /**\n     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\n     */\n    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n\n    /**\n     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\n     */\n    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n    /**\n     * @dev Returns the number of tokens in ``owner``'s account.\n     */\n    function balanceOf(address owner) external view returns (uint256 balance);\n\n    /**\n     * @dev Returns the owner of the `tokenId` token.\n     *\n     * Requirements:\n     *\n     * - `tokenId` must exist.\n     */\n    function ownerOf(uint256 tokenId) external view returns (address owner);\n\n    /**\n     * @dev Safely transfers `tokenId` token from `from` to `to`.\n     *\n     * Requirements:\n     *\n     * - `from` cannot be the zero address.\n     * - `to` cannot be the zero address.\n     * - `tokenId` token must exist and be owned by `from`.\n     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon\n     *   a safe transfer.\n     *\n     * Emits a {Transfer} event.\n     */\n    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;\n\n    /**\n     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n     * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n     *\n     * Requirements:\n     *\n     * - `from` cannot be the zero address.\n     * - `to` cannot be the zero address.\n     * - `tokenId` token must exist and be owned by `from`.\n     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or\n     *   {setApprovalForAll}.\n     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon\n     *   a safe transfer.\n     *\n     * Emits a {Transfer} event.\n     */\n    function safeTransferFrom(address from, address to, uint256 tokenId) external;\n\n    /**\n     * @dev Transfers `tokenId` token from `from` to `to`.\n     *\n     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721\n     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must\n     * understand this adds an external call which potentially creates a reentrancy vulnerability.\n     *\n     * Requirements:\n     *\n     * - `from` cannot be the zero address.\n     * - `to` cannot be the zero address.\n     * - `tokenId` token must be owned by `from`.\n     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n     *\n     * Emits a {Transfer} event.\n     */\n    function transferFrom(address from, address to, uint256 tokenId) external;\n\n    /**\n     * @dev Gives permission to `to` to transfer `tokenId` token to another account.\n     * The approval is cleared when the token is transferred.\n     *\n     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\n     *\n     * Requirements:\n     *\n     * - The caller must own the token or be an approved operator.\n     * - `tokenId` must exist.\n     *\n     * Emits an {Approval} event.\n     */\n    function approve(address to, uint256 tokenId) external;\n\n    /**\n     * @dev Approve or remove `operator` as an operator for the caller.\n     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\n     *\n     * Requirements:\n     *\n     * - The `operator` cannot be the address zero.\n     *\n     * Emits an {ApprovalForAll} event.\n     */\n    function setApprovalForAll(address operator, bool approved) external;\n\n    /**\n     * @dev Returns the account approved for `tokenId` token.\n     *\n     * Requirements:\n     *\n     * - `tokenId` must exist.\n     */\n    function getApproved(uint256 tokenId) external view returns (address operator);\n\n    /**\n     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\n     *\n     * See {setApprovalForAll}\n     */\n    function isApprovedForAll(address owner, address operator) external view returns (bool);\n}\n"
      },
      "@openzeppelin-5.0/contracts/token/ERC721/IERC721Receiver.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @title ERC721 token receiver interface\n * @dev Interface for any contract that wants to support safeTransfers\n * from ERC721 asset contracts.\n */\ninterface IERC721Receiver {\n    /**\n     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}\n     * by `operator` from `from`, this function is called.\n     *\n     * It must return its Solidity selector to confirm the token transfer.\n     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be\n     * reverted.\n     *\n     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.\n     */\n    function onERC721Received(\n        address operator,\n        address from,\n        uint256 tokenId,\n        bytes calldata data\n    ) external returns (bytes4);\n}\n"
      },
      "@openzeppelin-5.0/contracts/utils/Context.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n    function _msgSender() internal view virtual returns (address) {\n        return msg.sender;\n    }\n\n    function _msgData() internal view virtual returns (bytes calldata) {\n        return msg.data;\n    }\n\n    function _contextSuffixLength() internal view virtual returns (uint256) {\n        return 0;\n    }\n}\n"
      },
      "@openzeppelin-5.0/contracts/utils/introspection/ERC165.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"./IERC165.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n */\nabstract contract ERC165 is IERC165 {\n    /**\n     * @dev See {IERC165-supportsInterface}.\n     */\n    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {\n        return interfaceId == type(IERC165).interfaceId;\n    }\n}\n"
      },
      "@openzeppelin-5.0/contracts/utils/introspection/IERC165.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n    /**\n     * @dev Returns true if this contract implements the interface defined by\n     * `interfaceId`. See the corresponding\n     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n     * to learn more about how these ids are created.\n     *\n     * This function call must use less than 30 000 gas.\n     */\n    function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n"
      },
      "@openzeppelin-5.0/contracts/utils/math/Math.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n    /**\n     * @dev Muldiv operation overflow.\n     */\n    error MathOverflowedMulDiv();\n\n    enum Rounding {\n        Floor, // Toward negative infinity\n        Ceil, // Toward positive infinity\n        Trunc, // Toward zero\n        Expand // Away from zero\n    }\n\n    /**\n     * @dev Returns the addition of two unsigned integers, with an overflow flag.\n     */\n    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n        unchecked {\n            uint256 c = a + b;\n            if (c < a) return (false, 0);\n            return (true, c);\n        }\n    }\n\n    /**\n     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.\n     */\n    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n        unchecked {\n            if (b > a) return (false, 0);\n            return (true, a - b);\n        }\n    }\n\n    /**\n     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.\n     */\n    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n        unchecked {\n            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\n            // benefit is lost if 'b' is also tested.\n            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\n            if (a == 0) return (true, 0);\n            uint256 c = a * b;\n            if (c / a != b) return (false, 0);\n            return (true, c);\n        }\n    }\n\n    /**\n     * @dev Returns the division of two unsigned integers, with a division by zero flag.\n     */\n    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n        unchecked {\n            if (b == 0) return (false, 0);\n            return (true, a / b);\n        }\n    }\n\n    /**\n     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.\n     */\n    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {\n        unchecked {\n            if (b == 0) return (false, 0);\n            return (true, a % b);\n        }\n    }\n\n    /**\n     * @dev Returns the largest of two numbers.\n     */\n    function max(uint256 a, uint256 b) internal pure returns (uint256) {\n        return a > b ? a : b;\n    }\n\n    /**\n     * @dev Returns the smallest of two numbers.\n     */\n    function min(uint256 a, uint256 b) internal pure returns (uint256) {\n        return a < b ? a : b;\n    }\n\n    /**\n     * @dev Returns the average of two numbers. The result is rounded towards\n     * zero.\n     */\n    function average(uint256 a, uint256 b) internal pure returns (uint256) {\n        // (a + b) / 2 can overflow.\n        return (a & b) + (a ^ b) / 2;\n    }\n\n    /**\n     * @dev Returns the ceiling of the division of two numbers.\n     *\n     * This differs from standard division with `/` in that it rounds towards infinity instead\n     * of rounding towards zero.\n     */\n    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n        if (b == 0) {\n            // Guarantee the same behavior as in a regular Solidity division.\n            return a / b;\n        }\n\n        // (a + b - 1) / b can overflow on addition, so we distribute.\n        return a == 0 ? 0 : (a - 1) / b + 1;\n    }\n\n    /**\n     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or\n     * denominator == 0.\n     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by\n     * Uniswap Labs also under MIT license.\n     */\n    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {\n        unchecked {\n            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use\n            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\n            // variables such that product = prod1 * 2^256 + prod0.\n            uint256 prod0 = x * y; // Least significant 256 bits of the product\n            uint256 prod1; // Most significant 256 bits of the product\n            assembly {\n                let mm := mulmod(x, y, not(0))\n                prod1 := sub(sub(mm, prod0), lt(mm, prod0))\n            }\n\n            // Handle non-overflow cases, 256 by 256 division.\n            if (prod1 == 0) {\n                // Solidity will revert if denominator == 0, unlike the div opcode on its own.\n                // The surrounding unchecked block does not change this fact.\n                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.\n                return prod0 / denominator;\n            }\n\n            // Make sure the result is less than 2^256. Also prevents denominator == 0.\n            if (denominator <= prod1) {\n                revert MathOverflowedMulDiv();\n            }\n\n            ///////////////////////////////////////////////\n            // 512 by 256 division.\n            ///////////////////////////////////////////////\n\n            // Make division exact by subtracting the remainder from [prod1 prod0].\n            uint256 remainder;\n            assembly {\n                // Compute remainder using mulmod.\n                remainder := mulmod(x, y, denominator)\n\n                // Subtract 256 bit number from 512 bit number.\n                prod1 := sub(prod1, gt(remainder, prod0))\n                prod0 := sub(prod0, remainder)\n            }\n\n            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.\n            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.\n\n            uint256 twos = denominator & (0 - denominator);\n            assembly {\n                // Divide denominator by twos.\n                denominator := div(denominator, twos)\n\n                // Divide [prod1 prod0] by twos.\n                prod0 := div(prod0, twos)\n\n                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.\n                twos := add(div(sub(0, twos), twos), 1)\n            }\n\n            // Shift in bits from prod1 into prod0.\n            prod0 |= prod1 * twos;\n\n            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such\n            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for\n            // four bits. That is, denominator * inv = 1 mod 2^4.\n            uint256 inverse = (3 * denominator) ^ 2;\n\n            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also\n            // works in modular arithmetic, doubling the correct bits in each step.\n            inverse *= 2 - denominator * inverse; // inverse mod 2^8\n            inverse *= 2 - denominator * inverse; // inverse mod 2^16\n            inverse *= 2 - denominator * inverse; // inverse mod 2^32\n            inverse *= 2 - denominator * inverse; // inverse mod 2^64\n            inverse *= 2 - denominator * inverse; // inverse mod 2^128\n            inverse *= 2 - denominator * inverse; // inverse mod 2^256\n\n            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\n            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is\n            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1\n            // is no longer required.\n            result = prod0 * inverse;\n            return result;\n        }\n    }\n\n    /**\n     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.\n     */\n    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {\n        uint256 result = mulDiv(x, y, denominator);\n        if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {\n            result += 1;\n        }\n        return result;\n    }\n\n    /**\n     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded\n     * towards zero.\n     *\n     * Inspired by Henry S. Warren, Jr.'s \"Hacker's Delight\" (Chapter 11).\n     */\n    function sqrt(uint256 a) internal pure returns (uint256) {\n        if (a == 0) {\n            return 0;\n        }\n\n        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.\n        //\n        // We know that the \"msb\" (most significant bit) of our target number `a` is a power of 2 such that we have\n        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.\n        //\n        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`\n        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`\n        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`\n        //\n        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.\n        uint256 result = 1 << (log2(a) >> 1);\n\n        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,\n        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at\n        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision\n        // into the expected uint128 result.\n        unchecked {\n            result = (result + a / result) >> 1;\n            result = (result + a / result) >> 1;\n            result = (result + a / result) >> 1;\n            result = (result + a / result) >> 1;\n            result = (result + a / result) >> 1;\n            result = (result + a / result) >> 1;\n            result = (result + a / result) >> 1;\n            return min(result, a / result);\n        }\n    }\n\n    /**\n     * @notice Calculates sqrt(a), following the selected rounding direction.\n     */\n    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\n        unchecked {\n            uint256 result = sqrt(a);\n            return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);\n        }\n    }\n\n    /**\n     * @dev Return the log in base 2 of a positive value rounded towards zero.\n     * Returns 0 if given 0.\n     */\n    function log2(uint256 value) internal pure returns (uint256) {\n        uint256 result = 0;\n        unchecked {\n            if (value >> 128 > 0) {\n                value >>= 128;\n                result += 128;\n            }\n            if (value >> 64 > 0) {\n                value >>= 64;\n                result += 64;\n            }\n            if (value >> 32 > 0) {\n                value >>= 32;\n                result += 32;\n            }\n            if (value >> 16 > 0) {\n                value >>= 16;\n                result += 16;\n            }\n            if (value >> 8 > 0) {\n                value >>= 8;\n                result += 8;\n            }\n            if (value >> 4 > 0) {\n                value >>= 4;\n                result += 4;\n            }\n            if (value >> 2 > 0) {\n                value >>= 2;\n                result += 2;\n            }\n            if (value >> 1 > 0) {\n                result += 1;\n            }\n        }\n        return result;\n    }\n\n    /**\n     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\n     * Returns 0 if given 0.\n     */\n    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\n        unchecked {\n            uint256 result = log2(value);\n            return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);\n        }\n    }\n\n    /**\n     * @dev Return the log in base 10 of a positive value rounded towards zero.\n     * Returns 0 if given 0.\n     */\n    function log10(uint256 value) internal pure returns (uint256) {\n        uint256 result = 0;\n        unchecked {\n            if (value >= 10 ** 64) {\n                value /= 10 ** 64;\n                result += 64;\n            }\n            if (value >= 10 ** 32) {\n                value /= 10 ** 32;\n                result += 32;\n            }\n            if (value >= 10 ** 16) {\n                value /= 10 ** 16;\n                result += 16;\n            }\n            if (value >= 10 ** 8) {\n                value /= 10 ** 8;\n                result += 8;\n            }\n            if (value >= 10 ** 4) {\n                value /= 10 ** 4;\n                result += 4;\n            }\n            if (value >= 10 ** 2) {\n                value /= 10 ** 2;\n                result += 2;\n            }\n            if (value >= 10 ** 1) {\n                result += 1;\n            }\n        }\n        return result;\n    }\n\n    /**\n     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n     * Returns 0 if given 0.\n     */\n    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\n        unchecked {\n            uint256 result = log10(value);\n            return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);\n        }\n    }\n\n    /**\n     * @dev Return the log in base 256 of a positive value rounded towards zero.\n     * Returns 0 if given 0.\n     *\n     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\n     */\n    function log256(uint256 value) internal pure returns (uint256) {\n        uint256 result = 0;\n        unchecked {\n            if (value >> 128 > 0) {\n                value >>= 128;\n                result += 16;\n            }\n            if (value >> 64 > 0) {\n                value >>= 64;\n                result += 8;\n            }\n            if (value >> 32 > 0) {\n                value >>= 32;\n                result += 4;\n            }\n            if (value >> 16 > 0) {\n                value >>= 16;\n                result += 2;\n            }\n            if (value >> 8 > 0) {\n                result += 1;\n            }\n        }\n        return result;\n    }\n\n    /**\n     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.\n     * Returns 0 if given 0.\n     */\n    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\n        unchecked {\n            uint256 result = log256(value);\n            return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);\n        }\n    }\n\n    /**\n     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.\n     */\n    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {\n        return uint8(rounding) % 2 == 1;\n    }\n}\n"
      },
      "@openzeppelin-5.0/contracts/utils/math/SignedMath.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Standard signed math utilities missing in the Solidity language.\n */\nlibrary SignedMath {\n    /**\n     * @dev Returns the largest of two signed numbers.\n     */\n    function max(int256 a, int256 b) internal pure returns (int256) {\n        return a > b ? a : b;\n    }\n\n    /**\n     * @dev Returns the smallest of two signed numbers.\n     */\n    function min(int256 a, int256 b) internal pure returns (int256) {\n        return a < b ? a : b;\n    }\n\n    /**\n     * @dev Returns the average of two signed numbers without overflow.\n     * The result is rounded towards zero.\n     */\n    function average(int256 a, int256 b) internal pure returns (int256) {\n        // Formula from the book \"Hacker's Delight\"\n        int256 x = (a & b) + ((a ^ b) >> 1);\n        return x + (int256(uint256(x) >> 255) & (a ^ b));\n    }\n\n    /**\n     * @dev Returns the absolute unsigned value of a signed value.\n     */\n    function abs(int256 n) internal pure returns (uint256) {\n        unchecked {\n            // must be unchecked in order to support `n = type(int256).min`\n            return uint256(n >= 0 ? n : -n);\n        }\n    }\n}\n"
      },
      "@openzeppelin-5.0/contracts/utils/Strings.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)\n\npragma solidity ^0.8.20;\n\nimport {Math} from \"./math/Math.sol\";\nimport {SignedMath} from \"./math/SignedMath.sol\";\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n    bytes16 private constant HEX_DIGITS = \"0123456789abcdef\";\n    uint8 private constant ADDRESS_LENGTH = 20;\n\n    /**\n     * @dev The `value` string doesn't fit in the specified `length`.\n     */\n    error StringsInsufficientHexLength(uint256 value, uint256 length);\n\n    /**\n     * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n     */\n    function toString(uint256 value) internal pure returns (string memory) {\n        unchecked {\n            uint256 length = Math.log10(value) + 1;\n            string memory buffer = new string(length);\n            uint256 ptr;\n            /// @solidity memory-safe-assembly\n            assembly {\n                ptr := add(buffer, add(32, length))\n            }\n            while (true) {\n                ptr--;\n                /// @solidity memory-safe-assembly\n                assembly {\n                    mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))\n                }\n                value /= 10;\n                if (value == 0) break;\n            }\n            return buffer;\n        }\n    }\n\n    /**\n     * @dev Converts a `int256` to its ASCII `string` decimal representation.\n     */\n    function toStringSigned(int256 value) internal pure returns (string memory) {\n        return string.concat(value < 0 ? \"-\" : \"\", toString(SignedMath.abs(value)));\n    }\n\n    /**\n     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n     */\n    function toHexString(uint256 value) internal pure returns (string memory) {\n        unchecked {\n            return toHexString(value, Math.log256(value) + 1);\n        }\n    }\n\n    /**\n     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n     */\n    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n        uint256 localValue = value;\n        bytes memory buffer = new bytes(2 * length + 2);\n        buffer[0] = \"0\";\n        buffer[1] = \"x\";\n        for (uint256 i = 2 * length + 1; i > 1; --i) {\n            buffer[i] = HEX_DIGITS[localValue & 0xf];\n            localValue >>= 4;\n        }\n        if (localValue != 0) {\n            revert StringsInsufficientHexLength(value, length);\n        }\n        return string(buffer);\n    }\n\n    /**\n     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal\n     * representation.\n     */\n    function toHexString(address addr) internal pure returns (string memory) {\n        return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);\n    }\n\n    /**\n     * @dev Returns true if the two strings are equal.\n     */\n    function equal(string memory a, string memory b) internal pure returns (bool) {\n        return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));\n    }\n}\n"
      },
      "contracts/engine/V3/GenArt721CoreV3_Engine_Flex.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\npragma solidity 0.8.22;\n\n// Created By: Art Blocks Inc.\n\nimport \"../../interfaces/v0.8.x/IRandomizer_V3CoreBase.sol\";\nimport \"../../interfaces/v0.8.x/IAdminACLV0_Extended.sol\";\nimport \"../../interfaces/v0.8.x/IGenArt721CoreContractV3_Engine_Flex.sol\";\nimport {IGenArt721CoreContractV3_ProjectFinance} from \"../../interfaces/v0.8.x/IGenArt721CoreContractV3_ProjectFinance.sol\";\nimport \"../../interfaces/v0.8.x/IGenArt721CoreContractExposesHashSeed.sol\";\nimport \"../../interfaces/v0.8.x/IDependencyRegistryCompatibleV0.sol\";\nimport {ISplitProviderV0} from \"../../interfaces/v0.8.x/ISplitProviderV0.sol\";\nimport {IBytecodeStorageReader_Base} from \"../../interfaces/v0.8.x/IBytecodeStorageReader_Base.sol\";\n\nimport \"@openzeppelin-5.0/contracts/utils/Strings.sol\";\nimport \"@openzeppelin-5.0/contracts/access/Ownable.sol\";\nimport {IERC2981} from \"@openzeppelin-5.0/contracts/interfaces/IERC2981.sol\";\nimport \"../../libs/v0.8.x/ERC721_PackedHashSeedV1.sol\";\nimport {BytecodeStorageWriter, BytecodeStorageReader} from \"../../libs/v0.8.x/BytecodeStorageV2.sol\";\nimport {V3FlexLib} from \"../../libs/v0.8.x/V3FlexLib.sol\";\nimport \"../../libs/v0.8.x/Bytes32Strings.sol\";\n\n/**\n * @title Art Blocks Engine Flex ERC-721 core contract, V3.\n * @author Art Blocks Inc.\n * @notice Privileged Roles and Ownership:\n * This contract is designed to be managed, with progressively limited powers\n * as a project progresses from active to locked.\n * Privileged roles and abilities are controlled by the admin ACL contract and\n * artists. Both of these roles hold extensive power and can arbitrarily\n * control and modify portions of projects, dependent upon project state. After\n * a project is locked, important project metadata fields are locked including\n * the project name, artist name, and script and display details. Edition size\n * can never be increased.\n * Care must be taken to ensure that the admin ACL contract and artist\n * addresses are secure behind a multi-sig or other access control mechanism.\n * ----------------------------------------------------------------------------\n * The following functions are restricted to the Admin ACL contract:\n * - updateArtblocksDependencyRegistryAddress\n * - updateArtblocksOnChainGeneratorAddress\n * - updateNextCoreContract\n * - updateProviderSalesAddresses\n * - updateProviderPrimarySalesPercentages (up to 100%)\n * - updateProviderDefaultSecondarySalesBPS (up to 100%)\n * - syncProviderSecondaryForProjectToDefaults\n * - updateMinterContract\n * - updateRandomizerAddress\n * - toggleProjectIsActive (note: artist may be configured to activate projects)\n * - addProject\n * - forbidNewProjects (forever forbidding new projects)\n * - updateDefaultBaseURI (used to initialize new project base URIs)\n * - updateSplitProvider\n * - updateBytecodeStorageReaderContract\n * - updateIPFSGateway\n * - updateArweaveGateway\n * ----------------------------------------------------------------------------\n * The following functions are restricted to either the Artist address or\n * the Admin ACL contract, only when the project is not locked:\n * - updateProjectName\n * - updateProjectArtistName\n * - updateProjectLicense\n * - Change project script via addProjectScript, addProjectScriptCompressed,\n *   updateProjectScript, updateProjectScriptCompressed,\n *   and removeProjectLastScript\n * - updateProjectScriptType\n * - updateProjectAspectRatio\n * ----------------------------------------------------------------------------\n * The following functions are restricted to only the Artist address:\n * - proposeArtistPaymentAddressesAndSplits (Note that this has to be accepted\n *   by adminAcceptArtistAddressesAndSplits to take effect, which is restricted\n *   to the Admin ACL contract, or the artist if the core contract owner has\n *   renounced ownership. Also note that a proposal will be automatically\n *   accepted if the artist only proposes changed payee percentages without\n *   modifying any payee addresses, or is only removing payee addresses, or\n *   if the global config `autoApproveArtistSplitProposals` is set to `true`.)\n * - toggleProjectIsPaused (note the artist can still mint while paused)\n * - updateProjectSecondaryMarketRoyaltyPercentage (up to\n     ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent)\n * - updateProjectWebsite\n * - updateProjectMaxInvocations (to a number greater than or equal to the\n *   current number of invocations, and less than current project maximum\n *   invocations)\n * - updateProjectBaseURI (controlling the base URI for tokens in the project)\n * ----------------------------------------------------------------------------\n * The following function is restricted to either the Admin ACL contract, or\n * the Artist address if the core contract owner has renounced ownership:\n * - adminAcceptArtistAddressesAndSplits\n * - updateProjectArtistAddress (owner ultimately controlling the project and\n *   its and-on revenue, unless owner has renounced ownership)\n * ----------------------------------------------------------------------------\n * The following function is restricted to the artist when a project is\n * unlocked, and only callable by Admin ACL contract when a project is locked:\n * - updateProjectDescription\n * ----------------------------------------------------------------------------\n * The following functions for managing external asset dependencies are restricted\n * to projects with external asset dependencies that are unlocked:\n * - lockProjectExternalAssetDependencies \n * - updateProjectExternalAssetDependency\n * - updateProjectExternalAssetDependencyOnChainCompressed\n * - updateProjectAssetDependencyOnChainAtAddress\n * - removeProjectExternalAssetDependency\n * - addProjectExternalAssetDependency\n * - addProjectExternalAssetDependencyOnChainCompressed\n * - addProjectAssetDependencyOnChainAtAddress\n * ----------------------------------------------------------------------------\n * The following function is restricted to owner calling directly:\n * - transferOwnership\n * - renounceOwnership\n * ----------------------------------------------------------------------------\n * The following configuration variables are set at time of contract deployment,\n * and not modifiable thereafter (immutable after the point of deployment):\n * - (bool) autoApproveArtistSplitProposals\n * ----------------------------------------------------------------------------\n * Additional admin and artist privileged roles may be described on minters,\n * registries, and other contracts that may interact with this core contract.\n */\ncontract GenArt721CoreV3_Engine_Flex is\n    ERC721_PackedHashSeedV1,\n    Ownable,\n    IERC2981,\n    IDependencyRegistryCompatibleV0,\n    IGenArt721CoreContractV3_Engine_Flex,\n    IGenArt721CoreContractV3_ProjectFinance,\n    IGenArt721CoreContractExposesHashSeed\n{\n    using BytecodeStorageWriter for string;\n    using BytecodeStorageWriter for bytes;\n    using Bytes32Strings for bytes32;\n    using Strings for uint256;\n    using Strings for address;\n    uint256 constant ONE_HUNDRED = 100;\n    uint256 constant ONE_MILLION = 1_000_000;\n    uint24 constant ONE_MILLION_UINT24 = 1_000_000;\n    uint256 constant FOUR_WEEKS_IN_SECONDS = 2_419_200;\n    uint8 constant AT_CHARACTER_CODE = uint8(bytes1(\"@\")); // 0x40\n\n    // numeric constants\n    uint256 constant MAX_PROVIDER_SECONDARY_SALES_BPS = 10000; // 10_000 BPS = 100%\n    uint256 constant ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE = 95; // 95%\n\n    /// pointer to next core contract associated with this contract\n    address public nextCoreContract;\n\n    /// Dependency registry managed by Art Blocks\n    address public artblocksDependencyRegistryAddress;\n    /// On chain generator managed by Art Blocks\n    address public artblocksOnChainGeneratorAddress;\n\n    /// ensure initialization can only be performed once\n    bool private _initialized;\n\n    /// current randomizer contract\n    IRandomizer_V3CoreBase public randomizerContract;\n\n    /// append-only array of all randomizer contract addresses ever used by\n    /// this contract\n    address[] private _historicalRandomizerAddresses;\n\n    /// admin ACL contract\n    IAdminACLV0 public adminACLContract;\n\n    struct Project {\n        uint24 invocations;\n        uint24 maxInvocations;\n        uint24 scriptCount;\n        // max uint64 ~= 1.8e19 sec ~= 570 billion years\n        uint64 completedTimestamp;\n        bool active;\n        bool paused;\n        string name;\n        string artist;\n        address descriptionAddress;\n        string website;\n        string license;\n        string projectBaseURI;\n        bytes32 scriptTypeAndVersion;\n        string aspectRatio;\n        // mapping from script index to address storing script in bytecode\n        mapping(uint256 => address) scriptBytecodeAddresses;\n    }\n\n    mapping(uint256 => Project) projects;\n\n    /// private mapping from project ID to project financial information. See\n    /// `projectIdToFinancials` getter for public access.\n    mapping(uint256 _projectId => ProjectFinance)\n        private _projectIdToFinancials;\n\n    /// hash of artist's proposed payment updates to be approved by admin\n    mapping(uint256 => bytes32) public proposedArtistAddressesAndSplitsHash;\n\n    /// The render provider payment address for all primary sales revenues\n    /// (packed)\n    address payable public renderProviderPrimarySalesAddress;\n    /// Percentage of primary sales revenue allocated to the render provider\n    /// (packed)\n    // packed uint: max of 100, max uint8 = 255\n    uint8 private _renderProviderPrimarySalesPercentage;\n    /// The platform provider payment address for all primary sales revenues\n    /// (packed)\n    address payable public platformProviderPrimarySalesAddress;\n    /// Percentage of primary sales revenue allocated to the platform provider\n    /// (packed)\n    // packed uint: max of 100, max uint8 = 255\n    uint8 private _platformProviderPrimarySalesPercentage;\n\n    /// @dev Note on \"default\" provider secondary values - the only way these can\n    /// be different on a per project basis is if admin updates these and then\n    /// does not call syncProviderSecondaryForProjectToDefaults for the project.\n    /// -----------------------------------------------------------------------\n    /// The default render provider payment address for all secondary sales royalty\n    /// revenues, for all new projects. Individual project payment info is defined\n    /// in each project's ProjectFinance struct.\n    /// Projects can be updated to this value by calling the\n    /// `syncProviderSecondaryForProjectToDefaults` function for each project.\n    address payable public defaultRenderProviderSecondarySalesAddress;\n    /// The default basis points allocated to render provider for all secondary\n    /// sales royalty revenues, for all new projects. Individual project\n    /// payment info is defined in each project's ProjectFinance struct.\n    /// Projects can be updated to this value by calling the\n    /// `syncProviderSecondaryForProjectToDefaults` function for each project.\n    uint256 public defaultRenderProviderSecondarySalesBPS;\n    /// The default platform provider payment address for all secondary sales royalty\n    /// revenues, for all new projects. Individual project payment info is defined\n    /// in each project's ProjectFinance struct.\n    /// Projects can be updated to this value by calling the\n    /// `syncProviderSecondaryForProjectToDefaults` function for each project.\n    address payable public defaultPlatformProviderSecondarySalesAddress;\n    /// The default basis points allocated to platform provider for all secondary\n    /// sales royalty revenues, for all new projects. Individual project\n    /// payment info is defined in each project's ProjectFinance struct.\n    /// Projects can be updated to this value by calling the\n    /// `syncProviderSecondaryForProjectToDefaults` function for each project.\n    uint256 public defaultPlatformProviderSecondarySalesBPS;\n    /// -----------------------------------------------------------------------\n\n    /// single minter allowed for this core contract\n    address public minterContract;\n\n    /// starting (initial) project ID on this contract configured\n    /// at time of deployment and intended to be immutable after initialization.\n    /// Not marked as immutable due to initialization requirements\n    /// under the ERC-1167 minimal proxy pattern, which necessitates\n    /// setting this value post-deployment.\n    uint256 public startingProjectId;\n\n    /// next project ID to be created\n    uint248 private _nextProjectId;\n\n    /// bool indicating if adding new projects is forbidden;\n    /// default behavior is to allow new projects\n    bool public newProjectsForbidden;\n\n    /// configuration variable set at time of deployment, intended to be\n    /// immutable after initialization, that determines whether or not\n    /// admin approval^ should be required to accept artist address change\n    /// proposals, or if these proposals should always auto-approve, as\n    /// determined by the business process requirements of the Engine\n    /// partner using this contract.\n    ///\n    /// ^does not apply in the case where contract-ownership itself is revoked\n    /// Not marked as immutable due to initialization requirements\n    /// under the ERC-1167 minimal proxy pattern, which necessitates\n    /// setting this value post-deployment.\n    bool public autoApproveArtistSplitProposals;\n\n    /// configuration variable set at time of deployment, intended to be\n    /// immutable after initialization, that determines if platform provider\n    /// fees and addresses are always required to be set to zero.\n    /// Not marked as immutable due to initialization requirements\n    /// under the ERC-1167 minimal proxy pattern, which necessitates\n    /// setting this value post-deployment.\n    bool public nullPlatformProvider;\n\n    /// configuration variable set at time of deployment, intended to be\n    /// immutable after initialization, that determines if artists are allowed\n    /// to activate their own projects.\n    /// Not marked as immutable due to initialization requirements\n    /// under the ERC-1167 minimal proxy pattern, which necessitates\n    /// setting this value post-deployment.\n    bool public allowArtistProjectActivation;\n\n    /// version & type of this core contract\n    bytes32 constant CORE_VERSION = \"v3.2.5\";\n\n    function coreVersion() external pure returns (string memory) {\n        return CORE_VERSION.toString();\n    }\n\n    bytes32 constant CORE_TYPE = \"GenArt721CoreV3_Engine_Flex\";\n\n    function coreType() external pure returns (string memory) {\n        return CORE_TYPE.toString();\n    }\n\n    /// default base URI to initialize all new project projectBaseURI values to\n    string public defaultBaseURI;\n\n    // ERC2981 royalty support and default royalty values\n    bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a;\n    uint8 private constant _DEFAULT_ARTIST_SECONDARY_ROYALTY_PERCENTAGE = 5;\n\n    // royalty split provider\n    ISplitProviderV0 public splitProvider;\n\n    // bytecode storage reader contract; may be universal or specific version reader contract\n    IBytecodeStorageReader_Base public bytecodeStorageReaderContract;\n\n    /**\n     * @dev This constructor sets the owner to a non-functional address as a formality.\n     * It is only ever ran on the implementation contract. The `Ownable` constructor is\n     * called to satisfy the contract's inheritance requirements. This owner has no\n     * operational significance and should not be considered secure or meaningful.\n     * The true ownership will be set in the `initialize` function post-deployment to\n     * ensure correct owner management in the proxy architecture.\n     * Explicitly setting the owner to '0xdead' to indicate non-operational use.\n     */\n    constructor() Ownable(0x000000000000000000000000000000000000dEaD) {}\n\n    function _onlyNonZeroAddress(address _address) internal pure {\n        if (_address == address(0)) {\n            revert GenArt721Error(ErrorCodes.OnlyNonZeroAddress);\n        }\n    }\n\n    function _onlyNonEmptyString(string memory _string) internal pure {\n        if (bytes(_string).length == 0) {\n            revert GenArt721Error(ErrorCodes.OnlyNonEmptyString);\n        }\n    }\n\n    function _onlyNonEmptyBytes(bytes memory _bytes) internal pure {\n        if (_bytes.length == 0) {\n            revert GenArt721Error(ErrorCodes.OnlyNonEmptyBytes);\n        }\n    }\n\n    function _onlyValidTokenId(uint256 _tokenId) internal view {\n        if (_ownerOf(_tokenId) == address(0)) {\n            revert GenArt721Error(ErrorCodes.TokenDoesNotExist);\n        }\n    }\n\n    function _onlyValidProjectId(uint256 _projectId) internal view {\n        if (_projectId < startingProjectId || _projectId >= _nextProjectId) {\n            revert GenArt721Error(ErrorCodes.ProjectDoesNotExist);\n        }\n    }\n\n    function _onlyUnlocked(uint256 _projectId) internal view {\n        // Note: calling `_projectUnlocked` enforces that the `_projectId`\n        //       passed in is valid.`\n        if (!_projectUnlocked(_projectId)) {\n            revert GenArt721Error(ErrorCodes.OnlyUnlockedProjects);\n        }\n    }\n\n    function _onlyAdminACL(bytes4 _selector) internal {\n        if (!adminACLAllowed(msg.sender, address(this), _selector)) {\n            revert GenArt721Error(ErrorCodes.OnlyAdminACL);\n        }\n    }\n\n    function _onlyArtist(uint256 _projectId) internal view {\n        if (msg.sender != _projectIdToFinancials[_projectId].artistAddress) {\n            revert GenArt721Error(ErrorCodes.OnlyArtist);\n        }\n    }\n\n    function _onlyArtistOrAdminACL(\n        uint256 _projectId,\n        bytes4 _selector\n    ) internal {\n        if (\n            !(msg.sender == _projectIdToFinancials[_projectId].artistAddress ||\n                adminACLAllowed(msg.sender, address(this), _selector))\n        ) {\n            revert GenArt721Error(ErrorCodes.OnlyArtistOrAdminACL);\n        }\n    }\n\n    /**\n     * This modifier allows the artist of a project to call a function if the\n     * owner of the contract has renounced ownership. This is to allow the\n     * contract to continue to function if the owner decides to renounce\n     * ownership.\n     */\n    function _onlyAdminACLOrRenouncedArtist(\n        uint256 _projectId,\n        bytes4 _selector\n    ) internal {\n        // check if Admin ACL is allowed to call this function\n        if (adminACLAllowed(msg.sender, address(this), _selector)) {\n            return;\n        }\n        // check if the owner has renounced ownership and the caller is the\n        // artist of the project\n        if (\n            owner() == address(0) &&\n            msg.sender == _projectIdToFinancials[_projectId].artistAddress\n        ) {\n            return;\n        }\n        // neither of the above conditions were met, revert\n        revert GenArt721Error(ErrorCodes.OnlyAdminACLOrRenouncedArtist);\n    }\n\n    /**\n     * @notice Initializes the contract with the provided `engineConfiguration`.\n     * This function should be called atomically, immediately after deployment.\n     * Only callable once. Validation on `engineConfiguration` is performed by caller.\n     * @dev This function is intentionally unpermissioned to allow for the\n     * initialization of the contract post-deployment. It is expected that this\n     * function will be called atomically by the factory contract that deploys this\n     * contract, after which it will be initialized and uncallable.\n     * @param engineConfiguration EngineConfiguration to configure the contract with.\n     * @param adminACLContract_ Address of admin access control contract, to be\n     * set as contract owner.\n     * @param defaultBaseURIHost Base URI prefix to initialize default base URI with.\n     * @param bytecodeStorageReaderContract_ Address of the bytecode storage reader contract.\n     */\n    function initialize(\n        EngineConfiguration memory engineConfiguration,\n        address adminACLContract_,\n        string memory defaultBaseURIHost,\n        address bytecodeStorageReaderContract_\n    ) external virtual {\n        // @dev internal function call so derived contracts have access to initialization logic\n        _initialize({\n            engineConfiguration: engineConfiguration,\n            adminACLContract_: adminACLContract_,\n            defaultBaseURIHost: defaultBaseURIHost,\n            bytecodeStorageReaderContract_: bytecodeStorageReaderContract_\n        });\n    }\n\n    /**\n     * @notice Updates preferredIPFSGateway to `_gateway`.\n     */\n    function updateIPFSGateway(string calldata _gateway) public {\n        _onlyAdminACL(this.updateIPFSGateway.selector);\n        V3FlexLib.updateIPFSGateway({_gateway: _gateway});\n    }\n\n    /**\n     * @notice Updates preferredArweaveGateway to `_gateway`.\n     */\n    function updateArweaveGateway(string calldata _gateway) public {\n        _onlyAdminACL(this.updateArweaveGateway.selector);\n        V3FlexLib.updateArweaveGateway({_gateway: _gateway});\n    }\n\n    /**\n     * @notice Locks external asset dependencies for project `_projectId`.\n     */\n    function lockProjectExternalAssetDependencies(uint256 _projectId) external {\n        _onlyArtistOrAdminACL(\n            _projectId,\n            this.lockProjectExternalAssetDependencies.selector\n        );\n        V3FlexLib.lockProjectExternalAssetDependencies({\n            _projectId: _projectId\n        });\n    }\n\n    /**\n     * @notice Updates external asset dependency for project `_projectId`.\n     * @param _projectId Project to be updated.\n     * @param _index Asset index.\n     * @param _cidOrData Field that contains the CID of the dependency if IPFS or ARWEAVE,\n     * empty string of ONCHAIN, or a string representation of the Art Blocks Dependency\n     * Registry's `dependencyNameAndVersion` if ART_BLOCKS_DEPENDENCY_REGISTRY.\n     * @param _dependencyType Asset dependency type.\n     *  0 - IPFS\n     *  1 - ARWEAVE\n     *  2 - ONCHAIN\n     *  3 - ART_BLOCKS_DEPENDENCY_REGISTRY\n     */\n    function updateProjectExternalAssetDependency(\n        uint256 _projectId,\n        uint256 _index,\n        string memory _cidOrData,\n        ExternalAssetDependencyType _dependencyType\n    ) external {\n        _onlyArtistOrAdminACL(\n            _projectId,\n            this.updateProjectExternalAssetDependency.selector\n        );\n        V3FlexLib.updateProjectExternalAssetDependency({\n            _projectId: _projectId,\n            _index: _index,\n            _cidOrData: _cidOrData,\n            _dependencyType: _dependencyType\n        });\n    }\n\n    /**\n     * @notice Updates external asset dependency for project `_projectId` of type\n     * ONCHAIN using on-chain compression. The string should be compressed using\n     * `getCompressed`.\n     * This function stores the string in a compressed format on-chain.\n     * For reads, the compressed script is decompressed on-chain, ensuring the\n     * original text is reconstructed without external dependencies.\n     * @dev _compressedString in memory to minimize bytecode size.\n     * @param _projectId Project to be updated.\n     * @param _index Asset index.\n     * @param _compressedString Pre-compressed string asset to be added.\n     */\n    function updateProjectExternalAssetDependencyOnChainCompressed(\n        uint256 _projectId,\n        uint256 _index,\n        bytes memory _compressedString\n    ) external {\n        _onlyArtistOrAdminACL(\n            _projectId,\n            this.updateProjectExternalAssetDependencyOnChainCompressed.selector\n        );\n        V3FlexLib.updateProjectExternalAssetDependencyOnChainCompressed({\n            _projectId: _projectId,\n            _index: _index,\n            _compressedString: _compressedString\n        });\n    }\n\n    /**\n     * @notice Updates external asset dependency for project `_projectId` at\n     * index `_index`, with data at BytecodeStorage-compatible address\n     * `_assetAddress`.\n     * @param _projectId Project to be updated.\n     * @param _index Asset index.\n     * @param _assetAddress Address of the on-chain asset.\n     */\n    function updateProjectAssetDependencyOnChainAtAddress(\n        uint256 _projectId,\n        uint256 _index,\n        address _assetAddress\n    ) external {\n        _onlyArtistOrAdminACL(\n            _projectId,\n            this.updateProjectAssetDependencyOnChainAtAddress.selector\n        );\n        V3FlexLib.updateProjectAssetDependencyOnChainAtAddress({\n            _projectId: _projectId,\n            _index: _index,\n            _assetAddress: _assetAddress\n        });\n    }\n\n    /**\n     * @notice Removes external asset dependency for project `_projectId` at index `_index`.\n     * As of v3.2, only allow removal of dependency at last index, for UX purposes.\n     * @param _projectId Project to be updated.\n     * @param _index Asset index\n     */\n    function removeProjectExternalAssetDependency(\n        uint256 _projectId,\n        uint256 _index\n    ) external {\n        _onlyArtistOrAdminACL(\n            _projectId,\n            this.removeProjectExternalAssetDependency.selector\n        );\n        V3FlexLib.removeProjectExternalAssetDependency({\n            _projectId: _projectId,\n            _index: _index\n        });\n    }\n\n    /**\n     * @notice Adds external asset dependency for project `_projectId`.\n     * @param _projectId Project to be updated.\n     * @param _cidOrData Field that contains the CID of the dependency if IPFS or ARWEAVE,\n     * empty string of ONCHAIN, or a string representation of the Art Blocks Dependency\n     * Registry's `dependencyNameAndVersion` if ART_BLOCKS_DEPENDENCY_REGISTRY.\n     * @param _dependencyType Asset dependency type.\n     *  0 - IPFS\n     *  1 - ARWEAVE\n     *  2 - ONCHAIN\n     *  3 - ART_BLOCKS_DEPENDENCY_REGISTRY\n     */\n    function addProjectExternalAssetDependency(\n        uint256 _projectId,\n        string memory _cidOrData,\n        ExternalAssetDependencyType _dependencyType\n    ) external {\n        _onlyArtistOrAdminACL(\n            _projectId,\n            this.addProjectExternalAssetDependency.selector\n        );\n        V3FlexLib.addProjectExternalAssetDependency({\n            _projectId: _projectId,\n            _cidOrData: _cidOrData,\n            _dependencyType: _dependencyType\n        });\n    }\n\n    /**\n     * @notice Adds external asset dependency for project `_projectId` of type\n     * ONCHAIN using on-chain compression. The string should be compressed using\n     * `getCompressed`.\n     * This function stores the string in a compressed format on-chain.\n     * For reads, the compressed script is decompressed on-chain, ensuring the\n     * original text is reconstructed without external dependencies.\n     * @dev _compressedString in memory to minimize bytecode size.\n     * @param _projectId Project to be updated.\n     * @param _compressedString Pre-compressed string asset to be added.\n     */\n    function addProjectExternalAssetDependencyOnChainCompressed(\n        uint256 _projectId,\n        bytes memory _compressedString\n    ) external {\n        _onlyArtistOrAdminACL(\n            _projectId,\n            this.addProjectExternalAssetDependencyOnChainCompressed.selector\n        );\n        V3FlexLib.addProjectExternalAssetDependencyOnChainCompressed({\n            _projectId: _projectId,\n            _compressedString: _compressedString\n        });\n    }\n\n    /**\n     * @notice Adds an on-chain external asset dependency for project\n     * `_projectId`, with data at BytecodeStorage-compatible address\n     * `_assetAddress`.\n     * @param _projectId Project to be updated.\n     * @param _assetAddress Address of the BytecodeStorageReader-compatible on-chain asset.\n     */\n    function addProjectAssetDependencyOnChainAtAddress(\n        uint256 _projectId,\n        address _assetAddress\n    ) external {\n        _onlyArtistOrAdminACL(\n            _projectId,\n            this.addProjectAssetDependencyOnChainAtAddress.selector\n        );\n        V3FlexLib.addProjectAssetDependencyOnChainAtAddress({\n            _projectId: _projectId,\n            _assetAddress: _assetAddress\n        });\n    }\n\n    /**\n     * @notice Mints a token from project `_projectId` and sets the\n     * token's owner to `_to`. Hash may or may not be assigned to the token\n     * during the mint transaction, depending on the randomizer contract.\n     * @param _to Address to be the minted token's owner.\n     * @param _projectId Project ID to mint a token on.\n     * @param _by Purchaser of minted token.\n     * @return _tokenId The ID of the minted token.\n     * @dev sender must be the allowed minterContract\n     * @dev name of function is optimized for gas usage\n     */\n    function mint_Ecf(\n        address _to,\n        uint256 _projectId,\n        address _by\n    ) external returns (uint256 _tokenId) {\n        // CHECKS\n        if (msg.sender != minterContract) {\n            revert GenArt721Error(ErrorCodes.OnlyMinterContract);\n        }\n        Project storage project = projects[_projectId];\n        // load invocations into memory\n        uint24 invocationsBefore = project.invocations;\n        uint24 invocationsAfter;\n        unchecked {\n            // invocationsBefore guaranteed <= maxInvocations <= 1_000_000,\n            // 1_000_000 << max uint24, so no possible overflow\n            invocationsAfter = invocationsBefore + 1;\n        }\n        uint24 maxInvocations = project.maxInvocations;\n        if (invocationsBefore >= maxInvocations) {\n            revert GenArt721Error(ErrorCodes.MaxInvocationsReached);\n        }\n        if (\n            !(project.active ||\n                _by == _projectIdToFinancials[_projectId].artistAddress)\n        ) {\n            revert GenArt721Error(ErrorCodes.ProjectMustExistAndBeActive);\n        }\n        if (\n            project.paused &&\n            _by != _projectIdToFinancials[_projectId].artistAddress\n        ) {\n            revert GenArt721Error(ErrorCodes.PurchasesPaused);\n        }\n\n        // EFFECTS\n        // increment project's invocations\n        project.invocations = invocationsAfter;\n        uint256 thisTokenId;\n        unchecked {\n            // invocationsBefore is uint24 << max uint256. In production use,\n            // _projectId * ONE_MILLION must be << max uint256, otherwise\n            // tokenIdToProjectId function become invalid.\n            // Therefore, no risk of overflow\n            thisTokenId = (_projectId * ONE_MILLION) + invocationsBefore;\n        }\n\n        // mark project as completed if hit max invocations\n        if (invocationsAfter == maxInvocations) {\n            _completeProject(_projectId);\n        }\n\n        // INTERACTIONS\n        _mint(_to, thisTokenId);\n\n        // token hash is updated by the randomizer contract on V3\n        randomizerContract.assignTokenHash(thisTokenId);\n\n        // Do not need to also log `projectId` in event, as the `projectId` for\n        // a given token can be derived from the `tokenId` with:\n        //   projectId = tokenId / 1_000_000\n        emit Mint(_to, thisTokenId);\n\n        return thisTokenId;\n    }\n\n    /**\n     * @notice Sets the hash seed for a given token ID `_tokenId`.\n     * May only be called by the current randomizer contract.\n     * May only be called for tokens that have not already been assigned a\n     * non-zero hash.\n     * @param _tokenId Token ID to set the hash for.\n     * @param _hashSeed Hash seed to set for the token ID. Only last 12 bytes\n     * will be used.\n     * @dev gas-optimized function name because called during mint sequence\n     * @dev if a separate event is required when the token hash is set, e.g.\n     * for indexing purposes, it must be emitted by the randomizer. This is to\n     * minimize gas when minting.\n     */\n    function setTokenHash_8PT(uint256 _tokenId, bytes32 _hashSeed) external {\n        _onlyValidTokenId(_tokenId);\n\n        OwnerAndHashSeed storage ownerAndHashSeed = _ownersAndHashSeeds[\n            _tokenId\n        ];\n        if (msg.sender != address(randomizerContract)) {\n            revert GenArt721Error(ErrorCodes.OnlyRandomizer);\n        }\n        if (ownerAndHashSeed.hashSeed != bytes12(0)) {\n            revert GenArt721Error(ErrorCodes.TokenHashAlreadySet);\n        }\n        if (_hashSeed == bytes12(0)) {\n            revert GenArt721Error(ErrorCodes.NoZeroHashSeed);\n        }\n        ownerAndHashSeed.hashSeed = bytes12(_hashSeed);\n    }\n\n    /**\n     * @notice Allows owner (AdminACL) to revoke ownership of the contract.\n     * Note that the contract is intended to continue to function after the\n     * owner renounces ownership, but no new projects will be able to be added.\n     * Renouncing ownership will leave the contract without an owner,\n     * thereby removing any functionality that is only available to the\n     * owner/AdminACL contract. The same is true for any dependent contracts\n     * that also integrate with the owner/AdminACL contract (e.g. potentially\n     * minter suite contracts, registry contracts, etc.).\n     * After renouncing ownership, artists will be in control of updates to\n     * their payment addresses and splits (see modifier\n     * onlyAdminACLOrRenouncedArtist`).\n     * While there is no currently intended reason to call this method based on\n     * typical Engine partner business practices, this method exists to allow\n     * artists to continue to maintain the limited set of contract\n     * functionality that exists post-project-lock in an environment in which\n     * there is no longer an admin maintaining this smart contract.\n     * @dev This function is intended to be called directly by the AdminACL,\n     * not by an address allowed by the AdminACL contract.\n     */\n    function renounceOwnership() public override onlyOwner {\n        // broadcast that new projects are no longer allowed (if not already)\n        _forbidNewProjects();\n        // renounce ownership viw Ownable\n        Ownable.renounceOwnership();\n    }\n\n    /**\n     * @notice Updates reference to next core contract, associated with this contract.\n     * @param _nextCoreContract Address of the next core contract\n     */\n    function updateNextCoreContract(address _nextCoreContract) external {\n        _onlyAdminACL(this.updateNextCoreContract.selector);\n        nextCoreContract = _nextCoreContract;\n        emit PlatformUpdated(\n            bytes32(uint256(PlatformUpdatedFields.FIELD_NEXT_CORE_CONTRACT))\n        );\n    }\n\n    /**\n     * @notice Updates reference to Art Blocks Dependency Registry contract.\n     * @param _artblocksDependencyRegistryAddress Address of new Dependency\n     * Registry.\n     */\n    function updateArtblocksDependencyRegistryAddress(\n        address _artblocksDependencyRegistryAddress\n    ) external {\n        _onlyAdminACL(this.updateArtblocksDependencyRegistryAddress.selector);\n        _onlyNonZeroAddress(_artblocksDependencyRegistryAddress);\n        artblocksDependencyRegistryAddress = _artblocksDependencyRegistryAddress;\n        emit PlatformUpdated(\n            bytes32(\n                uint256(\n                    PlatformUpdatedFields\n                        .FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS\n                )\n            )\n        );\n    }\n\n    /**\n     * @notice Updates reference to Art Blocks On Chain Generator contract.\n     * @param _artblocksOnChainGeneratorAddress Address of new on chain generator.\n     */\n    function updateArtblocksOnChainGeneratorAddress(\n        address _artblocksOnChainGeneratorAddress\n    ) external {\n        _onlyAdminACL(this.updateArtblocksOnChainGeneratorAddress.selector);\n        _onlyNonZeroAddress(_artblocksOnChainGeneratorAddress);\n        artblocksOnChainGeneratorAddress = _artblocksOnChainGeneratorAddress;\n        emit PlatformUpdated(\n            bytes32(\n                uint256(\n                    PlatformUpdatedFields\n                        .FIELD_ARTBLOCKS_ON_CHAIN_GENERATOR_ADDRESS\n                )\n            )\n        );\n    }\n\n    /**\n     * @notice Updates sales addresses for the platform and render providers to\n     * the input parameters.\n     * note: This does not update splitter contracts for all projects on\n     * this core contract. If updated splitter contracts are desired, they must be\n     * updated after this update via the `syncProviderSecondaryForProjectToDefaults` function.\n     * @param _renderProviderPrimarySalesAddress Address of new primary sales\n     * payment address.\n     * @param _defaultRenderProviderSecondarySalesAddress Default address of new secondary sales\n     * payment address.\n     * @param _platformProviderPrimarySalesAddress Address of new primary sales\n     * payment address.\n     * @param _defaultPlatformProviderSecondarySalesAddress Default address of new secondary sales\n     * payment address.\n     */\n    function updateProviderSalesAddresses(\n        address payable _renderProviderPrimarySalesAddress,\n        address payable _defaultRenderProviderSecondarySalesAddress,\n        address payable _platformProviderPrimarySalesAddress,\n        address payable _defaultPlatformProviderSecondarySalesAddress\n    ) external {\n        _onlyAdminACL(this.updateProviderSalesAddresses.selector);\n        _onlyNonZeroAddress(_renderProviderPrimarySalesAddress);\n        _onlyNonZeroAddress(_defaultRenderProviderSecondarySalesAddress);\n        // @dev checks on platform provider addresses performed in _updateProviderSalesAddresses\n        _updateProviderSalesAddresses(\n            _renderProviderPrimarySalesAddress,\n            _defaultRenderProviderSecondarySalesAddress,\n            _platformProviderPrimarySalesAddress,\n            _defaultPlatformProviderSecondarySalesAddress\n        );\n    }\n\n    /**\n     * @notice Updates the render and platform provider primary sales revenue percentage to\n     * the provided inputs.\n     * If contract is configured to have a null platform provider, the platform provider\n     * primary sales percentage must be set to zero.\n     * @param renderProviderPrimarySalesPercentage_ New primary sales revenue % for the render provider\n     * @param platformProviderPrimarySalesPercentage_ New primary sales revenue % for the platform provider\n     * percentage.\n     */\n    function updateProviderPrimarySalesPercentages(\n        uint256 renderProviderPrimarySalesPercentage_,\n        uint256 platformProviderPrimarySalesPercentage_\n    ) external {\n        _onlyAdminACL(this.updateProviderPrimarySalesPercentages.selector);\n        // require no platform provider payment if null platform provider\n        if (\n            nullPlatformProvider && platformProviderPrimarySalesPercentage_ != 0\n        ) {\n            revert GenArt721Error(ErrorCodes.OnlyNullPlatformProvider);\n        }\n\n        // Validate that the sum of the proposed %s, does not exceed 100%.\n        if (\n            (renderProviderPrimarySalesPercentage_ +\n                platformProviderPrimarySalesPercentage_) > ONE_HUNDRED\n        ) {\n            revert GenArt721Error(ErrorCodes.OverMaxSumOfPercentages);\n        }\n        // Casting to `uint8` here is safe due check above, which does not allow\n        // overflow as of solidity version ^0.8.0.\n        _renderProviderPrimarySalesPercentage = uint8(\n            renderProviderPrimarySalesPercentage_\n        );\n        _platformProviderPrimarySalesPercentage = uint8(\n            platformProviderPrimarySalesPercentage_\n        );\n        emit PlatformUpdated(\n            bytes32(\n                uint256(\n                    PlatformUpdatedFields\n                        .FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES\n                )\n            )\n        );\n    }\n\n    /**\n     * @notice Updates default render and platform provider secondary sales royalty\n     * Basis Points to the provided inputs.\n     * If contract is configured to have a null platform provider, the platform provider\n     * secondary sales BPS must be set to zero.\n     * note: This does not update splitter contracts for all projects on\n     * this core contract. If updated splitter contracts are desired, they must be\n     * updated after this update via the `syncProviderSecondaryForProjectToDefaults` function.\n     * @param _defaultRenderProviderSecondarySalesBPS New default secondary sales royalty Basis\n     * points.\n     * @param _defaultPlatformProviderSecondarySalesBPS New default secondary sales royalty Basis\n     * points.\n     * @dev Due to secondary royalties being ultimately enforced via social\n     * consensus, no hard upper limit is imposed on the BPS value, other than\n     * <= 100% royalty, which would not make mathematical sense. Realistically,\n     * changing this value is expected to either never occur, or be a rare\n     * occurrence.\n     */\n    function updateProviderDefaultSecondarySalesBPS(\n        uint256 _defaultRenderProviderSecondarySalesBPS,\n        uint256 _defaultPlatformProviderSecondarySalesBPS\n    ) external {\n        _onlyAdminACL(this.updateProviderDefaultSecondarySalesBPS.selector);\n        // require no platform provider payment if null platform provider\n        if (\n            nullPlatformProvider &&\n            _defaultPlatformProviderSecondarySalesBPS != 0\n        ) {\n            revert GenArt721Error(ErrorCodes.OnlyNullPlatformProvider);\n        }\n        // Validate that the sum of the proposed provider BPS, does not exceed 10_000 BPS.\n        if (\n            _defaultRenderProviderSecondarySalesBPS +\n                _defaultPlatformProviderSecondarySalesBPS >\n            MAX_PROVIDER_SECONDARY_SALES_BPS\n        ) {\n            revert GenArt721Error(ErrorCodes.OverMaxSumOfBPS);\n        }\n        defaultRenderProviderSecondarySalesBPS = _defaultRenderProviderSecondarySalesBPS;\n        defaultPlatformProviderSecondarySalesBPS = _defaultPlatformProviderSecondarySalesBPS;\n        emit PlatformUpdated(\n            bytes32(\n                uint256(\n                    PlatformUpdatedFields.FIELD_PROVIDER_SECONDARY_SALES_BPS\n                )\n            )\n        );\n    }\n\n    /**\n     * @notice Updates minter to `_address`.\n     * @param _address Address of new minter.\n     */\n    function updateMinterContract(address _address) external {\n        _onlyAdminACL(this.updateMinterContract.selector);\n        _onlyNonZeroAddress(_address);\n        _updateMinterContract(_address);\n    }\n\n    /**\n     * @notice Updates randomizer to `_randomizerAddress`.\n     * @param _randomizerAddress Address of new randomizer.\n     */\n    function updateRandomizerAddress(address _randomizerAddress) external {\n        _onlyAdminACL(this.updateRandomizerAddress.selector);\n        _onlyNonZeroAddress(_randomizerAddress);\n        _updateRandomizerAddress(_randomizerAddress);\n    }\n\n    /**\n     * @notice Updates split provider address to `_splitProviderAddress`.\n     * Reverts if `_splitProviderAddress` is zero address.\n     * @param _splitProviderAddress New split provider address.\n     */\n    function updateSplitProvider(address _splitProviderAddress) external {\n        _onlyAdminACL(this.updateSplitProvider.selector);\n        _updateSplitProvider(_splitProviderAddress);\n    }\n\n    /**\n     * @notice Updates bytecode storage reader contract to `_bytecodeStorageReaderContract`.\n     * Reverts if `_bytecodeStorageReaderContract` is zero address.\n     * Updating the active Bytecode Storage Reader contract may affect the ability to read\n     * data related to existing projects. Care should be taken to ensure that the new\n     * contract is compatible with the existing project data.\n     * @param _bytecodeStorageReaderContract New bytecode storage reader contract address.\n     */\n    function updateBytecodeStorageReaderContract(\n        address _bytecodeStorageReaderContract\n    ) external {\n        _onlyAdminACL(this.updateBytecodeStorageReaderContract.selector);\n        _onlyNonZeroAddress(_bytecodeStorageReaderContract);\n        _updateBytecodeStorageReaderContract(_bytecodeStorageReaderContract);\n    }\n\n    /**\n     * @notice Toggles project `_projectId` as active/inactive.\n     * @param _projectId Project ID to be toggled.\n     */\n    function toggleProjectIsActive(uint256 _projectId) external {\n        if (allowArtistProjectActivation) {\n            _onlyArtistOrAdminACL(\n                _projectId,\n                this.toggleProjectIsActive.selector\n            );\n        } else {\n            _onlyAdminACL(this.toggleProjectIsActive.selector);\n        }\n        _onlyValidProjectId(_projectId);\n        projects[_projectId].active = !projects[_projectId].active;\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_ACTIVE))\n        );\n    }\n\n    /**\n     * @notice Artist proposes updated set of artist address, additional payee\n     * addresses, and percentage splits for project `_projectId`. Addresses and\n     * percentages do not have to all be changed, but they must all be defined\n     * as a complete set.\n     * Note that if the artist is only proposing a change to the payee percentage\n     * splits, without modifying the payee addresses, the proposal will be\n     * automatically approved and the new splits will become active immediately.\n     * Automatic approval will also be granted if the artist is only removing\n     * additional payee addresses, without adding any new ones.\n     * Also note that if `autoApproveArtistSplitProposals` is true, proposals\n     * will always be auto-approved, regardless of what is being changed.\n     * Also note that if the artist is proposing sending funds to the zero\n     * address, this function will revert and the proposal will not be created.\n     * @param _projectId Project ID.\n     * @param _artistAddress Artist address that controls the project, and may\n     * receive payments.\n     * @param _additionalPayeePrimarySales Address that may receive a\n     * percentage split of the artist's primary sales revenue.\n     * @param _additionalPayeePrimarySalesPercentage Percent of artist's\n     * portion of primary sale revenue that will be split to address\n     * `_additionalPayeePrimarySales`.\n     * @param _additionalPayeeSecondarySales Address that may receive a percentage\n     * split of the secondary sales royalties.\n     * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion\n     * of secondary sale royalties that will be split to address\n     * `_additionalPayeeSecondarySales`.\n     * @dev `_artistAddress` must be a valid address (non-zero-address), but it\n     * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`\n     * and their associated percentages to be zero'd out by the controlling artist.\n     */\n    function proposeArtistPaymentAddressesAndSplits(\n        uint256 _projectId,\n        address payable _artistAddress,\n        address payable _additionalPayeePrimarySales,\n        uint256 _additionalPayeePrimarySalesPercentage,\n        address payable _additionalPayeeSecondarySales,\n        uint256 _additionalPayeeSecondarySalesPercentage\n    ) external {\n        _onlyValidProjectId(_projectId);\n        _onlyArtist(_projectId);\n        _onlyNonZeroAddress(_artistAddress);\n        ProjectFinance storage projectFinance = _projectIdToFinancials[\n            _projectId\n        ];\n        // checks\n        if (\n            _additionalPayeePrimarySalesPercentage > ONE_HUNDRED ||\n            _additionalPayeeSecondarySalesPercentage > ONE_HUNDRED\n        ) {\n            revert GenArt721Error(ErrorCodes.MaxOf100Percent);\n        }\n        if (\n            _additionalPayeePrimarySalesPercentage > 0 &&\n            _additionalPayeePrimarySales == address(0)\n        ) {\n            revert GenArt721Error(ErrorCodes.PrimaryPayeeIsZeroAddress);\n        }\n        if (\n            _additionalPayeeSecondarySalesPercentage > 0 &&\n            _additionalPayeeSecondarySales == address(0)\n        ) {\n            revert GenArt721Error(ErrorCodes.SecondaryPayeeIsZeroAddress);\n        }\n        // effects\n        // emit event for off-chain indexing\n        // note: always emit a proposal event, even in the pathway of\n        // automatic approval, to simplify indexing expectations\n        emit ProposedArtistAddressesAndSplits(\n            _projectId,\n            _artistAddress,\n            _additionalPayeePrimarySales,\n            _additionalPayeePrimarySalesPercentage,\n            _additionalPayeeSecondarySales,\n            _additionalPayeeSecondarySalesPercentage\n        );\n        // automatically accept if no proposed addresses modifications, or if\n        // the proposal only removes payee addresses, or if contract is set to\n        // always auto-approve.\n        // store proposal hash on-chain, only if not automatic accept\n        bool automaticAccept = autoApproveArtistSplitProposals;\n        if (!automaticAccept) {\n            // block scope to avoid stack too deep error\n            bool artistUnchanged = _artistAddress ==\n                projectFinance.artistAddress;\n            bool additionalPrimaryUnchangedOrRemoved = (_additionalPayeePrimarySales ==\n                    projectFinance.additionalPayeePrimarySales) ||\n                    (_additionalPayeePrimarySales == address(0));\n            bool additionalSecondaryUnchangedOrRemoved = (_additionalPayeeSecondarySales ==\n                    projectFinance.additionalPayeeSecondarySales) ||\n                    (_additionalPayeeSecondarySales == address(0));\n            automaticAccept =\n                artistUnchanged &&\n                additionalPrimaryUnchangedOrRemoved &&\n                additionalSecondaryUnchangedOrRemoved;\n        }\n        if (automaticAccept) {\n            // clear any previously proposed values\n            proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);\n\n            // update storage\n            // artist address can change during automatic accept if\n            // autoApproveArtistSplitProposals is true\n            projectFinance.artistAddress = _artistAddress;\n            projectFinance\n                .additionalPayeePrimarySales = _additionalPayeePrimarySales;\n            // safe to cast as uint8 as max is 100%, max uint8 is 255\n            projectFinance.additionalPayeePrimarySalesPercentage = uint8(\n                _additionalPayeePrimarySalesPercentage\n            );\n            projectFinance\n                .additionalPayeeSecondarySales = _additionalPayeeSecondarySales;\n            // safe to cast as uint8 as max is 100%, max uint8 is 255\n            projectFinance.additionalPayeeSecondarySalesPercentage = uint8(\n                _additionalPayeeSecondarySalesPercentage\n            );\n\n            // assign project's splitter\n            // @dev only call after all previous storage updates\n            _assignSplitter(_projectId);\n\n            // emit event for off-chain indexing\n            emit AcceptedArtistAddressesAndSplits(_projectId);\n        } else {\n            proposedArtistAddressesAndSplitsHash[_projectId] = keccak256(\n                abi.encode(\n                    _artistAddress,\n                    _additionalPayeePrimarySales,\n                    _additionalPayeePrimarySalesPercentage,\n                    _additionalPayeeSecondarySales,\n                    _additionalPayeeSecondarySalesPercentage\n                )\n            );\n        }\n    }\n\n    /**\n     * @notice Admin accepts a proposed set of updated artist address,\n     * additional payee addresses, and percentage splits for project\n     * `_projectId`. Addresses and percentages do not have to all be changed,\n     * but they must all be defined as a complete set.\n     * @param _projectId Project ID.\n     * @param _artistAddress Artist address that controls the project, and may\n     * receive payments.\n     * @param _additionalPayeePrimarySales Address that may receive a\n     * percentage split of the artist's primary sales revenue.\n     * @param _additionalPayeePrimarySalesPercentage Percent of artist's\n     * portion of primary sale revenue that will be split to address\n     * `_additionalPayeePrimarySales`.\n     * @param _additionalPayeeSecondarySales Address that may receive a percentage\n     * split of the secondary sales royalties.\n     * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion\n     * of secondary sale royalties that will be split to address\n     * `_additionalPayeeSecondarySales`.\n     * @dev this must be called by the Admin ACL contract, and must only accept\n     * the most recent proposed values for a given project (validated on-chain\n     * by comparing the hash of the proposed and accepted values).\n     * @dev `_artistAddress` must be a valid address (non-zero-address), but it\n     * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`\n     * and their associated percentages to be zero'd out by the controlling artist.\n     */\n    function adminAcceptArtistAddressesAndSplits(\n        uint256 _projectId,\n        address payable _artistAddress,\n        address payable _additionalPayeePrimarySales,\n        uint256 _additionalPayeePrimarySalesPercentage,\n        address payable _additionalPayeeSecondarySales,\n        uint256 _additionalPayeeSecondarySalesPercentage\n    ) external {\n        _onlyValidProjectId(_projectId);\n        _onlyAdminACLOrRenouncedArtist(\n            _projectId,\n            this.adminAcceptArtistAddressesAndSplits.selector\n        );\n        _onlyNonZeroAddress(_artistAddress);\n        // checks\n        if (\n            proposedArtistAddressesAndSplitsHash[_projectId] !=\n            keccak256(\n                abi.encode(\n                    _artistAddress,\n                    _additionalPayeePrimarySales,\n                    _additionalPayeePrimarySalesPercentage,\n                    _additionalPayeeSecondarySales,\n                    _additionalPayeeSecondarySalesPercentage\n                )\n            )\n        ) {\n            revert GenArt721Error(ErrorCodes.MustMatchArtistProposal);\n        }\n        // effects\n        ProjectFinance storage projectFinance = _projectIdToFinancials[\n            _projectId\n        ];\n        projectFinance.artistAddress = _artistAddress;\n        projectFinance\n            .additionalPayeePrimarySales = _additionalPayeePrimarySales;\n        projectFinance.additionalPayeePrimarySalesPercentage = uint8(\n            _additionalPayeePrimarySalesPercentage\n        );\n        projectFinance\n            .additionalPayeeSecondarySales = _additionalPayeeSecondarySales;\n        projectFinance.additionalPayeeSecondarySalesPercentage = uint8(\n            _additionalPayeeSecondarySalesPercentage\n        );\n        // clear proposed values\n        proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);\n\n        // assign project's splitter\n        // @dev only call after all previous storage updates\n        _assignSplitter(_projectId);\n\n        // emit event for off-chain indexing\n        emit AcceptedArtistAddressesAndSplits(_projectId);\n    }\n\n    /**\n     * @notice Updates artist of project `_projectId` to `_artistAddress`.\n     * This is to only be used in the event that the artist address is\n     * compromised or sanctioned.\n     * @param _projectId Project ID.\n     * @param _artistAddress New artist address.\n     */\n    function updateProjectArtistAddress(\n        uint256 _projectId,\n        address payable _artistAddress\n    ) external {\n        _onlyValidProjectId(_projectId);\n        _onlyAdminACLOrRenouncedArtist(\n            _projectId,\n            this.updateProjectArtistAddress.selector\n        );\n        _onlyNonZeroAddress(_artistAddress);\n\n        _projectIdToFinancials[_projectId].artistAddress = _artistAddress;\n\n        // assign project's splitter\n        // @dev only call after all previous storage updates\n        _assignSplitter(_projectId);\n\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_ARTIST_ADDRESS))\n        );\n    }\n\n    /**\n     * @notice Toggles paused state of project `_projectId`.\n     * @param _projectId Project ID to be toggled.\n     */\n    function toggleProjectIsPaused(uint256 _projectId) external {\n        _onlyArtist(_projectId);\n        projects[_projectId].paused = !projects[_projectId].paused;\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_PAUSED))\n        );\n    }\n\n    /**\n     * @notice Adds new project `_projectName` by `_artistAddress`.\n     * @param _projectName Project name.\n     * @param _artistAddress Artist's address.\n     * @dev token price now stored on minter\n     */\n    function addProject(\n        string memory _projectName,\n        address payable _artistAddress\n    ) external {\n        _onlyAdminACL(this.addProject.selector);\n        _onlyNonEmptyString(_projectName);\n        _onlyNonZeroAddress(_artistAddress);\n        if (newProjectsForbidden) {\n            revert GenArt721Error(ErrorCodes.NewProjectsForbidden);\n        }\n        uint256 projectId = _nextProjectId;\n        ProjectFinance storage projectFinance = _projectIdToFinancials[\n            projectId\n        ];\n        projectFinance.artistAddress = _artistAddress;\n        projects[projectId].name = _projectName;\n        projects[projectId].paused = true;\n        projects[projectId].maxInvocations = ONE_MILLION_UINT24;\n        projects[projectId].projectBaseURI = defaultBaseURI;\n        // assign default artist royalty to artist\n        projectFinance\n            .secondaryMarketRoyaltyPercentage = _DEFAULT_ARTIST_SECONDARY_ROYALTY_PERCENTAGE;\n        // copy default platform and render provider royalties to ProjectFinance\n        projectFinance\n            .platformProviderSecondarySalesAddress = defaultPlatformProviderSecondarySalesAddress;\n        projectFinance.platformProviderSecondarySalesBPS = uint16(\n            defaultPlatformProviderSecondarySalesBPS\n        );\n        projectFinance\n            .renderProviderSecondarySalesAddress = defaultRenderProviderSecondarySalesAddress;\n        projectFinance.renderProviderSecondarySalesBPS = uint16(\n            defaultRenderProviderSecondarySalesBPS\n        );\n\n        _nextProjectId = uint248(projectId) + 1;\n\n        // @dev emit initial project created event before splitter event\n        emit ProjectUpdated(\n            projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_CREATED))\n        );\n\n        // assign project's splitter\n        // @dev only call after all previous storage updates\n        _assignSplitter(projectId);\n    }\n\n    /**\n     * @notice Forever forbids new projects from being added to this contract.\n     */\n    function forbidNewProjects() external {\n        _onlyAdminACL(this.forbidNewProjects.selector);\n        if (newProjectsForbidden) {\n            revert GenArt721Error(ErrorCodes.NewProjectsAlreadyForbidden);\n        }\n        _forbidNewProjects();\n    }\n\n    /**\n     * @notice Updates name of project `_projectId` to be `_projectName`.\n     * @param _projectId Project ID.\n     * @param _projectName New project name.\n     */\n    function updateProjectName(\n        uint256 _projectId,\n        string memory _projectName\n    ) external {\n        _onlyUnlocked(_projectId);\n        _onlyArtistOrAdminACL(_projectId, this.updateProjectName.selector);\n        _onlyNonEmptyString(_projectName);\n        projects[_projectId].name = _projectName;\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_NAME))\n        );\n    }\n\n    /**\n     * @notice Updates artist name for project `_projectId` to be\n     * `_projectArtistName`.\n     * @dev allows admin to update after project is locked, due to our\n     * experiences of artist name changes being requested post-lock.\n     * @param _projectId Project ID.\n     * @param _projectArtistName New artist name.\n     */\n    function updateProjectArtistName(\n        uint256 _projectId,\n        string memory _projectArtistName\n    ) external {\n        // if unlocked, only artist may update, if locked, only admin may update\n        // @dev valid project checked in _projectUnlocked function\n        if (_projectUnlocked(_projectId)) {\n            if (\n                msg.sender != _projectIdToFinancials[_projectId].artistAddress\n            ) {\n                revert GenArt721Error(ErrorCodes.OnlyArtistOrAdminIfLocked);\n            }\n        } else {\n            if (\n                !adminACLAllowed(\n                    msg.sender,\n                    address(this),\n                    this.updateProjectArtistName.selector\n                )\n            ) {\n                revert GenArt721Error(ErrorCodes.OnlyArtistOrAdminIfLocked);\n            }\n        }\n        _onlyNonEmptyString(_projectArtistName);\n        projects[_projectId].artist = _projectArtistName;\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_ARTIST_NAME))\n        );\n    }\n\n    /**\n     * @notice Updates artist secondary market royalties for project\n     * `_projectId` to be `_secondaryMarketRoyalty` percent.\n     * This deploys a new splitter contract if needed.\n     * This DOES NOT include the secondary market royalty percentages collected\n     * by the issuing platform; it is only the total percentage of royalties\n     * that will be split to artist and additionalSecondaryPayee.\n     * @param _projectId Project ID.\n     * @param _secondaryMarketRoyalty Percent of secondary sales revenue that will\n     * be split to artist and additionalSecondaryPayee. This must be less than\n     * or equal to ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent.\n     */\n    function updateProjectSecondaryMarketRoyaltyPercentage(\n        uint256 _projectId,\n        uint256 _secondaryMarketRoyalty\n    ) external {\n        _onlyArtist(_projectId);\n        if (_secondaryMarketRoyalty > ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE) {\n            revert GenArt721Error(ErrorCodes.OverMaxSecondaryRoyaltyPercentage);\n        }\n        _projectIdToFinancials[_projectId]\n            .secondaryMarketRoyaltyPercentage = uint8(_secondaryMarketRoyalty);\n\n        // assign project's splitter\n        // @dev only call after all previous storage updates\n        _assignSplitter(_projectId);\n\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(\n                uint256(\n                    ProjectUpdatedFields\n                        .FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE\n                )\n            )\n        );\n    }\n\n    /**\n     * @notice Updates platform and render provider secondary market royalty addresses\n     * and BPS to the contract-level default values for project `_projectId`.\n     * This updates the splitter parameters on the existing splitter for the project.\n     * Reverts if called by a non-admin address.\n     * @param _projectId Project ID.\n     */\n    function syncProviderSecondaryForProjectToDefaults(\n        uint256 _projectId\n    ) external {\n        _onlyAdminACL(this.syncProviderSecondaryForProjectToDefaults.selector);\n        _onlyValidProjectId(_projectId);\n        ProjectFinance storage projectFinance = _projectIdToFinancials[\n            _projectId\n        ];\n        // update project finance for project in storage\n        projectFinance\n            .platformProviderSecondarySalesAddress = defaultPlatformProviderSecondarySalesAddress;\n        projectFinance.platformProviderSecondarySalesBPS = uint16(\n            defaultPlatformProviderSecondarySalesBPS\n        );\n        projectFinance\n            .renderProviderSecondarySalesAddress = defaultRenderProviderSecondarySalesAddress;\n        projectFinance.renderProviderSecondarySalesBPS = uint16(\n            defaultRenderProviderSecondarySalesBPS\n        );\n\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(\n                uint256(\n                    ProjectUpdatedFields\n                        .FIELD_PROJECT_PROVIDER_SECONDARY_FINANCIALS\n                )\n            )\n        );\n\n        // assign project's splitter\n        // @dev only call after all previous storage updates\n        _assignSplitter(_projectId);\n    }\n\n    /**\n     * @notice Updates description of project `_projectId`.\n     * Only artist may call when unlocked, only admin may call when locked.\n     * Note: The BytecodeStorage library is used to store the description to\n     * reduce initial upload cost, however, even minor edits will require an\n     * expensive, entirely new bytecode storage contract to be deployed instead\n     * of relatively cheap updates to already-warm storage slots. This results\n     * in an increased gas cost for minor edits to the description after the\n     * initial upload, but an overall decrease in gas cost for projects with\n     * less than ~3-5 edits (depending on the length of the description).\n     * @param _projectId Project ID.\n     * @param _projectDescription New project description.\n     */\n    function updateProjectDescription(\n        uint256 _projectId,\n        string memory _projectDescription\n    ) external {\n        // checks\n        // if unlocked, only artist may update, if locked, only admin may update\n        if (_projectUnlocked(_projectId)) {\n            if (\n                msg.sender != _projectIdToFinancials[_projectId].artistAddress\n            ) {\n                revert GenArt721Error(ErrorCodes.OnlyArtistOrAdminIfLocked);\n            }\n        } else {\n            if (\n                !adminACLAllowed(\n                    msg.sender,\n                    address(this),\n                    this.updateProjectDescription.selector\n                )\n            ) {\n                revert GenArt721Error(ErrorCodes.OnlyArtistOrAdminIfLocked);\n            }\n        }\n        // effects\n        // store description in contract bytecode, replacing reference address from\n        // the old storage description with the newly created one\n        projects[_projectId].descriptionAddress = _projectDescription\n            .writeToBytecode();\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_DESCRIPTION))\n        );\n    }\n\n    /**\n     * @notice Updates website of project `_projectId` to be `_projectWebsite`.\n     * @param _projectId Project ID.\n     * @param _projectWebsite New project website.\n     * @dev It is intentionally allowed for this to be set to the empty string.\n     */\n    function updateProjectWebsite(\n        uint256 _projectId,\n        string memory _projectWebsite\n    ) external {\n        _onlyArtist(_projectId);\n        projects[_projectId].website = _projectWebsite;\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_WEBSITE))\n        );\n    }\n\n    /**\n     * @notice Updates license for project `_projectId`.\n     * @param _projectId Project ID.\n     * @param _projectLicense New project license.\n     */\n    function updateProjectLicense(\n        uint256 _projectId,\n        string memory _projectLicense\n    ) external {\n        _onlyUnlocked(_projectId);\n        _onlyArtistOrAdminACL(_projectId, this.updateProjectLicense.selector);\n        _onlyNonEmptyString(_projectLicense);\n        projects[_projectId].license = _projectLicense;\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_LICENSE))\n        );\n    }\n\n    /**\n     * @notice Updates maximum invocations for project `_projectId` to\n     * `_maxInvocations`. Maximum invocations may only be decreased by the\n     * artist, and must be greater than or equal to current invocations.\n     * New projects are created with maximum invocations of 1 million by\n     * default.\n     * @param _projectId Project ID.\n     * @param _maxInvocations New maximum invocations.\n     */\n    function updateProjectMaxInvocations(\n        uint256 _projectId,\n        uint24 _maxInvocations\n    ) external {\n        _onlyArtist(_projectId);\n        // CHECKS\n        Project storage project = projects[_projectId];\n        uint256 _invocations = project.invocations;\n        if (_maxInvocations >= project.maxInvocations) {\n            revert GenArt721Error(ErrorCodes.OnlyMaxInvocationsDecrease);\n        }\n        if (_maxInvocations < _invocations) {\n            revert GenArt721Error(ErrorCodes.OnlyGteInvocations);\n        }\n        // EFFECTS\n        project.maxInvocations = _maxInvocations;\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_MAX_INVOCATIONS))\n        );\n\n        // register completed timestamp if action completed the project\n        if (_maxInvocations == _invocations) {\n            _completeProject(_projectId);\n        }\n    }\n\n    /**\n     * @notice Adds a script to project `_projectId`.\n     * @param _projectId Project to be updated.\n     * @param _script Script to be added. Required to be a non-empty string,\n     * but no further validation is performed.\n     */\n    function addProjectScript(\n        uint256 _projectId,\n        string memory _script\n    ) external {\n        _onlyUnlocked(_projectId);\n        _onlyArtistOrAdminACL(_projectId, this.addProjectScript.selector);\n        _onlyNonEmptyString(_script);\n        Project storage project = projects[_projectId];\n        // store script in contract bytecode\n        project.scriptBytecodeAddresses[project.scriptCount] = _script\n            .writeToBytecode();\n        project.scriptCount = project.scriptCount + 1;\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_SCRIPT))\n        );\n    }\n\n    /**\n     * @notice Adds a pre-compressed script to project `_projectId`. The script\n     * should be compressed using `getCompressed`. This function stores the script\n     * in a compressed format on-chain. For reads, the compressed script is\n     * decompressed on-chain, ensuring the original text is reconstructed without\n     * external dependencies.\n     * @param _projectId Project to be updated.\n     * @param _compressedScript Pre-compressed script to be added.\n     * Required to be non-empty, but no further validation is performed.\n     */\n    function addProjectScriptCompressed(\n        uint256 _projectId,\n        bytes memory _compressedScript\n    ) external {\n        _onlyUnlocked(_projectId);\n        _onlyArtistOrAdminACL(\n            _projectId,\n            this.addProjectScriptCompressed.selector\n        );\n        _onlyNonEmptyBytes(_compressedScript);\n        Project storage project = projects[_projectId];\n        // store compressed script in contract bytecode\n        project.scriptBytecodeAddresses[project.scriptCount] = _compressedScript\n            .writeToBytecodeCompressed();\n        project.scriptCount = project.scriptCount + 1;\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_SCRIPT))\n        );\n    }\n\n    /**\n     * @notice Updates script for project `_projectId` at script ID `_scriptId`.\n     * @param _projectId Project to be updated.\n     * @param _scriptId Script ID to be updated.\n     * @param _script The updated script value. Required to be a non-empty\n     *                string, but no further validation is performed.\n     */\n    function updateProjectScript(\n        uint256 _projectId,\n        uint256 _scriptId,\n        string memory _script\n    ) external {\n        _onlyUnlocked(_projectId);\n        _onlyArtistOrAdminACL(_projectId, this.updateProjectScript.selector);\n        _onlyNonEmptyString(_script);\n        Project storage project = projects[_projectId];\n        if (_scriptId >= project.scriptCount) {\n            revert GenArt721Error(ErrorCodes.ScriptIdOutOfRange);\n        }\n        // store script in contract bytecode, replacing reference address from\n        // the old storage contract with the newly created one\n        project.scriptBytecodeAddresses[_scriptId] = _script.writeToBytecode();\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_SCRIPT))\n        );\n    }\n\n    /**\n     * @notice Updates script for project `_projectId` at script ID `_scriptId`\n     * with a pre-compressed script. The script should be compressed using\n     * `getCompressed`. This function stores the script in a compressed format\n     * on-chain. For reads, the compressed script is decompressed on-chain, ensuring\n     * the original text is reconstructed without external dependencies.\n     * @param _projectId Project to be updated.\n     * @param _scriptId Script ID to be updated.\n     * @param _compressedScript The updated pre-compressed script value.\n     * Required to be non-empty, but no further validation is performed.\n     */\n    function updateProjectScriptCompressed(\n        uint256 _projectId,\n        uint256 _scriptId,\n        bytes memory _compressedScript\n    ) external {\n        _onlyUnlocked(_projectId);\n        _onlyArtistOrAdminACL(\n            _projectId,\n            this.updateProjectScriptCompressed.selector\n        );\n        _onlyNonEmptyBytes(_compressedScript);\n        Project storage project = projects[_projectId];\n        if (_scriptId >= project.scriptCount) {\n            revert GenArt721Error(ErrorCodes.ScriptIdOutOfRange);\n        }\n        // store script in contract bytecode, replacing reference address from\n        // the old storage contract with the newly created one\n        project.scriptBytecodeAddresses[_scriptId] = _compressedScript\n            .writeToBytecodeCompressed();\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_SCRIPT))\n        );\n    }\n\n    /**\n     * @notice Removes last script from project `_projectId`.\n     * @param _projectId Project to be updated.\n     */\n    function removeProjectLastScript(uint256 _projectId) external {\n        _onlyUnlocked(_projectId);\n        _onlyArtistOrAdminACL(\n            _projectId,\n            this.removeProjectLastScript.selector\n        );\n        Project storage project = projects[_projectId];\n        if (project.scriptCount == 0) {\n            revert GenArt721Error(ErrorCodes.NoScriptsToRemove);\n        }\n        // delete reference to old storage contract address\n        delete project.scriptBytecodeAddresses[project.scriptCount - 1];\n        unchecked {\n            project.scriptCount = project.scriptCount - 1;\n        }\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_SCRIPT))\n        );\n    }\n\n    /**\n     * @notice Updates script type for project `_projectId`.\n     * @param _projectId Project to be updated.\n     * @param _scriptTypeAndVersion Script type and version e.g. \"p5js@1.0.0\",\n     * as bytes32 encoded string.\n     */\n    function updateProjectScriptType(\n        uint256 _projectId,\n        bytes32 _scriptTypeAndVersion\n    ) external {\n        _onlyUnlocked(_projectId);\n        _onlyArtistOrAdminACL(\n            _projectId,\n            this.updateProjectScriptType.selector\n        );\n        Project storage project = projects[_projectId];\n        // require exactly one @ symbol in _scriptTypeAndVersion\n        if (\n            !_scriptTypeAndVersion.containsExactCharacterQty(\n                AT_CHARACTER_CODE,\n                uint8(1)\n            )\n        ) {\n            revert GenArt721Error(ErrorCodes.ScriptTypeAndVersionFormat);\n        }\n        project.scriptTypeAndVersion = _scriptTypeAndVersion;\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_SCRIPT_TYPE))\n        );\n    }\n\n    /**\n     * @notice Updates project's aspect ratio.\n     * @param _projectId Project to be updated.\n     * @param _aspectRatio Aspect ratio to be set. Intended to be string in the\n     * format of a decimal, e.g. \"1\" for square, \"1.77777778\" for 16:9, etc.,\n     * allowing for a maximum of 10 digits and one (optional) decimal separator.\n     */\n    function updateProjectAspectRatio(\n        uint256 _projectId,\n        string memory _aspectRatio\n    ) external {\n        _onlyUnlocked(_projectId);\n        _onlyArtistOrAdminACL(\n            _projectId,\n            this.updateProjectAspectRatio.selector\n        );\n        _onlyNonEmptyString(_aspectRatio);\n        // Perform more detailed input validation for aspect ratio.\n        bytes memory aspectRatioBytes = bytes(_aspectRatio);\n        uint256 bytesLength = aspectRatioBytes.length;\n        if (bytesLength > 11) {\n            revert GenArt721Error(ErrorCodes.AspectRatioTooLong);\n        }\n        bool hasSeenDecimalSeparator = false;\n        bool hasSeenNumber = false;\n        for (uint256 i; i < bytesLength; i++) {\n            bytes1 character = aspectRatioBytes[i];\n            // Allow as many #s as desired.\n            if (character >= 0x30 && character <= 0x39) {\n                // 9-0\n                // We need to ensure there is at least 1 `9-0` occurrence.\n                hasSeenNumber = true;\n                continue;\n            }\n            if (character == 0x2E) {\n                // .\n                // Allow no more than 1 `.` occurrence.\n                if (!hasSeenDecimalSeparator) {\n                    hasSeenDecimalSeparator = true;\n                    continue;\n                }\n            }\n            revert GenArt721Error(ErrorCodes.AspectRatioImproperFormat);\n        }\n        if (!hasSeenNumber) {\n            revert GenArt721Error(ErrorCodes.AspectRatioNoNumbers);\n        }\n\n        projects[_projectId].aspectRatio = _aspectRatio;\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_ASPECT_RATIO))\n        );\n    }\n\n    /**\n     * @notice Updates base URI for project `_projectId` to `_newBaseURI`.\n     * This is the controlling base URI for all tokens in the project. The\n     * contract-level defaultBaseURI is only used when initializing new\n     * projects.\n     * @param _projectId Project to be updated.\n     * @param _newBaseURI New base URI.\n     */\n    function updateProjectBaseURI(\n        uint256 _projectId,\n        string memory _newBaseURI\n    ) external {\n        _onlyArtist(_projectId);\n        _onlyNonEmptyString(_newBaseURI);\n        projects[_projectId].projectBaseURI = _newBaseURI;\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_BASE_URI))\n        );\n    }\n\n    /**\n     * @notice Updates default base URI to `_defaultBaseURI`. The\n     * contract-level defaultBaseURI is only used when initializing new\n     * projects. Token URIs are determined by their project's `projectBaseURI`.\n     * @param _defaultBaseURI New default base URI.\n     */\n    function updateDefaultBaseURI(string memory _defaultBaseURI) external {\n        _onlyAdminACL(this.updateDefaultBaseURI.selector);\n        _onlyNonEmptyString(_defaultBaseURI);\n        _updateDefaultBaseURI(_defaultBaseURI);\n    }\n\n    /**\n     * @notice Next project ID to be created on this contract.\n     * @return uint256 Next project ID.\n     */\n    function nextProjectId() external view returns (uint256) {\n        return _nextProjectId;\n    }\n\n    /**\n     * @notice Returns token hash for token ID `_tokenId`. Returns null if hash\n     * has not been set.\n     * @param _tokenId Token ID to be queried.\n     * @return bytes32 Token hash.\n     * @dev token hash is the keccak256 hash of the stored hash seed\n     */\n    function tokenIdToHash(uint256 _tokenId) external view returns (bytes32) {\n        bytes12 _hashSeed = _ownersAndHashSeeds[_tokenId].hashSeed;\n        if (_hashSeed == 0) {\n            return 0;\n        }\n        return keccak256(abi.encode(_hashSeed));\n    }\n\n    /**\n     * @notice Returns token hash **seed** for token ID `_tokenId`. Returns\n     * null if hash seed has not been set. The hash seed id the bytes12 value\n     * which is hashed to produce the token hash.\n     * @param _tokenId Token ID to be queried.\n     * @return bytes12 Token hash seed.\n     * @dev token hash seed is keccak256 hashed to give the token hash\n     */\n    function tokenIdToHashSeed(\n        uint256 _tokenId\n    ) external view returns (bytes12) {\n        return _ownersAndHashSeeds[_tokenId].hashSeed;\n    }\n\n    /**\n     * @notice View function returning the render provider portion of\n     * primary sales, in percent.\n     * @return uint256 The render provider portion of primary sales,\n     * in percent.\n     */\n    function renderProviderPrimarySalesPercentage()\n        external\n        view\n        returns (uint256)\n    {\n        return _renderProviderPrimarySalesPercentage;\n    }\n\n    /**\n     * @notice View function returning the platform provider portion of\n     * primary sales, in percent.\n     * @return uint256 The platform provider portion of primary sales,\n     * in percent.\n     */\n    function platformProviderPrimarySalesPercentage()\n        external\n        view\n        returns (uint256)\n    {\n        return _platformProviderPrimarySalesPercentage;\n    }\n\n    /**\n     * @notice View function returning Artist's address for project\n     * `_projectId`.\n     * @param _projectId Project ID to be queried.\n     * @return address Artist's address.\n     */\n    function projectIdToArtistAddress(\n        uint256 _projectId\n    ) external view returns (address payable) {\n        return _projectIdToFinancials[_projectId].artistAddress;\n    }\n\n    /**\n     * @notice View function returning Artist's secondary market royalty\n     * percentage for project `_projectId`.\n     * This does not include render/platform providers portions of secondary\n     * market royalties.\n     * @param _projectId Project ID to be queried.\n     * @return uint256 Artist's secondary market royalty percentage.\n     */\n    function projectIdToSecondaryMarketRoyaltyPercentage(\n        uint256 _projectId\n    ) external view returns (uint256) {\n        return\n            _projectIdToFinancials[_projectId].secondaryMarketRoyaltyPercentage;\n    }\n\n    /**\n     * @notice View function returning project financial details for project\n     * `_projectId`.\n     * @param _projectId Project ID to be queried.\n     * @return ProjectFinance Project financial details.\n     */\n    function projectIdToFinancials(\n        uint256 _projectId\n    ) external view returns (ProjectFinance memory) {\n        return _projectIdToFinancials[_projectId];\n    }\n\n    /**\n     * @notice Returns project details for project `_projectId`.\n     * @param _projectId Project to be queried.\n     * @return projectName Name of project\n     * @return artist Artist of project\n     * @return description Project description\n     * @return website Project website\n     * @return license Project license\n     * @dev this function was named projectDetails prior to V3 core contract.\n     */\n    function projectDetails(\n        uint256 _projectId\n    )\n        external\n        view\n        returns (\n            string memory projectName,\n            string memory artist,\n            string memory description,\n            string memory website,\n            string memory license\n        )\n    {\n        Project storage project = projects[_projectId];\n        projectName = project.name;\n        artist = project.artist;\n        address projectDescriptionBytecodeAddress = project.descriptionAddress;\n        if (projectDescriptionBytecodeAddress == address(0)) {\n            description = \"\";\n        } else {\n            description = _readFromBytecode(projectDescriptionBytecodeAddress);\n        }\n        website = project.website;\n        license = project.license;\n    }\n\n    /**\n     * @notice Returns project state data for project `_projectId`.\n     * @param _projectId Project to be queried\n     * @return invocations Current number of invocations\n     * @return maxInvocations Maximum allowed invocations\n     * @return active Boolean representing if project is currently active\n     * @return paused Boolean representing if project is paused\n     * @return completedTimestamp zero if project not complete, otherwise\n     * timestamp of project completion.\n     * @return locked Boolean representing if project is locked\n     * @dev price and currency info are located on minter contracts\n     */\n    function projectStateData(\n        uint256 _projectId\n    )\n        external\n        view\n        returns (\n            uint256 invocations,\n            uint256 maxInvocations,\n            bool active,\n            bool paused,\n            uint256 completedTimestamp,\n            bool locked\n        )\n    {\n        Project storage project = projects[_projectId];\n        invocations = project.invocations;\n        maxInvocations = project.maxInvocations;\n        active = project.active;\n        paused = project.paused;\n        completedTimestamp = project.completedTimestamp;\n        locked = !_projectUnlocked(_projectId);\n    }\n\n    /**\n     * @notice Returns script information for project `_projectId`.\n     * @param _projectId Project to be queried.\n     * @return scriptTypeAndVersion Project's script type and version\n     * (e.g. \"p5js(atSymbol)1.0.0\")\n     * @return aspectRatio Aspect ratio of project (e.g. \"1\" for square,\n     * \"1.77777778\" for 16:9, etc.)\n     * @return scriptCount Count of scripts for project\n     */\n    function projectScriptDetails(\n        uint256 _projectId\n    )\n        external\n        view\n        override(IGenArt721CoreContractV3_Base, IDependencyRegistryCompatibleV0)\n        returns (\n            string memory scriptTypeAndVersion,\n            string memory aspectRatio,\n            uint256 scriptCount\n        )\n    {\n        Project storage project = projects[_projectId];\n        scriptTypeAndVersion = project.scriptTypeAndVersion.toString();\n        aspectRatio = project.aspectRatio;\n        scriptCount = project.scriptCount;\n    }\n\n    /**\n     * @notice Returns address with bytecode containing project script for\n     * project `_projectId` at script index `_index`.\n     */\n    function projectScriptBytecodeAddressByIndex(\n        uint256 _projectId,\n        uint256 _index\n    ) external view returns (address) {\n        return projects[_projectId].scriptBytecodeAddresses[_index];\n    }\n\n    /**\n     * @notice Returns the compressed form of a string in bytes using solady LibZip's flz compress algorithm. The bytes output from this function are intended to be used as input to `addProjectScriptCompressed` and `updateProjectScriptCompressed`.\n     * @param _script Script to be compressed. Required to be a non-empty string, but no further validaton is performed.\n     * @return bytes compressed bytes\n     */\n    function getCompressed(\n        string memory _script\n    ) external pure returns (bytes memory) {\n        _onlyNonEmptyString(_script);\n        // @dev want a potentially version-specific compression algorithm, so use version-specific library here\n        return BytecodeStorageReader.getCompressed(_script);\n    }\n\n    /**\n     * @notice Returns script for project `_projectId` at script index `_index`.\n     * @param _projectId Project to be queried.\n     * @param _index Index of script to be queried.\n     */\n    function projectScriptByIndex(\n        uint256 _projectId,\n        uint256 _index\n    ) external view returns (string memory) {\n        Project storage project = projects[_projectId];\n        // If trying to access an out-of-index script, return the empty string.\n        if (_index >= project.scriptCount) {\n            return \"\";\n        }\n        return _readFromBytecode(project.scriptBytecodeAddresses[_index]);\n    }\n\n    /**\n     * @notice Returns base URI for project `_projectId`.\n     * @param _projectId Project to be queried.\n     * @return projectBaseURI Base URI for project\n     */\n    function projectURIInfo(\n        uint256 _projectId\n    ) external view returns (string memory projectBaseURI) {\n        projectBaseURI = projects[_projectId].projectBaseURI;\n    }\n\n    /**\n     * @notice Backwards-compatible (pre-V3) function returning if `_minter` is\n     * minterContract.\n     * @param _minter Address to be queried.\n     * @return bool Boolean representing if `_minter` is minterContract.\n     */\n    function isMintWhitelisted(address _minter) external view returns (bool) {\n        return (minterContract == _minter);\n    }\n\n    /**\n     * @notice Gets qty of randomizers in history of all randomizers used by\n     * this core contract. If a randomizer is switched away from then back to,\n     * it will show up in the history twice.\n     * @return randomizerHistoryCount Count of randomizers in history\n     */\n    function numHistoricalRandomizers() external view returns (uint256) {\n        return _historicalRandomizerAddresses.length;\n    }\n\n    /**\n     * @notice Gets address of randomizer at index `_index` in history of all\n     * randomizers used by this core contract. Index is zero-based.\n     * @param _index Historical index of randomizer to be queried.\n     * @return randomizerAddress Address of randomizer at index `_index`.\n     * @dev If a randomizer is switched away from and then switched back to, it\n     * will show up in the history twice.\n     */\n    function getHistoricalRandomizerAt(\n        uint256 _index\n    ) external view returns (address) {\n        if (_index >= _historicalRandomizerAddresses.length) {\n            revert GenArt721Error(ErrorCodes.IndexOutOfBounds);\n        }\n        return _historicalRandomizerAddresses[_index];\n    }\n\n    /**\n     * @notice Gets ERC-2981 royalty information for token with ID `_tokenId`\n     * and sale price `_salePrice`.\n     * @param _tokenId Token ID to be queried for royalty information\n     * @param _salePrice the sale price of the NFT asset specified by _tokenId\n     * @return receiver address that should be sent the royalty payment\n     * @return royaltyAmount the royalty payment amount for `_salePrice\n     * @dev reverts if invalid _tokenId\n     */\n    function royaltyInfo(\n        uint256 _tokenId,\n        uint256 _salePrice\n    ) external view returns (address receiver, uint256 royaltyAmount) {\n        _onlyValidTokenId(_tokenId);\n\n        // populate receiver with project's royalty splitter\n        // @dev royalty splitter created upon project creation, so will always exist\n        // for valid token ID\n        uint256 projectId = tokenIdToProjectId(_tokenId);\n        ProjectFinance storage projectFinance = _projectIdToFinancials[\n            projectId\n        ];\n        receiver = projectFinance.royaltySplitter;\n\n        // populate royaltyAmount with calculated royalty amount\n        // @dev important to cast to uint256 before multiplying to avoid overflow\n        uint256 totalRoyaltyBPS = (100 *\n            uint256(projectFinance.secondaryMarketRoyaltyPercentage)) +\n            projectFinance.platformProviderSecondarySalesBPS +\n            projectFinance.renderProviderSecondarySalesBPS;\n        // @dev totalRoyaltyBPS guaranteed to be <= 10,000,\n        if (totalRoyaltyBPS > 10_000) {\n            revert GenArt721Error(ErrorCodes.OverMaxSumOfBPS);\n        }\n        // @dev overflow automatically checked in solidity 0.8\n        // @dev totalRoyaltyBPS guaranteed to be <= 10_000,\n        // so overflow only possible with unreasonably high _salePrice values near uint256 max\n        royaltyAmount = (_salePrice * totalRoyaltyBPS) / 10_000;\n    }\n\n    /**\n     * @notice View function that returns appropriate revenue splits between\n     * different render provider, platform provider, Artist, and Artist's\n     * additional primary sales payee given a sale price of `_price` on\n     * project `_projectId`.\n     * This always returns four revenue amounts and four addresses, but if a\n     * revenue is zero for either Artist or additional payee, the corresponding\n     * address returned will also be null (for gas optimization).\n     * Does not account for refund if user overpays for a token (minter should\n     * handle a refund of the difference, if appropriate).\n     * Some minters may have alternative methods of splitting payments, in\n     * which case they should implement their own payment splitting logic.\n     * @param _projectId Project ID to be queried.\n     * @param _price Sale price of token.\n     * @return renderProviderRevenue_ amount of revenue to be sent to the\n     * render provider\n     * @return renderProviderAddress_ address to send render provider revenue to\n     * @return platformProviderRevenue_ amount of revenue to be sent to the\n     * platform provider\n     * @return platformProviderAddress_ address to send platform provider revenue to\n     * @return artistRevenue_ amount of revenue to be sent to Artist\n     * @return artistAddress_ address to send Artist revenue to. Will be null\n     * if no revenue is due to artist (gas optimization).\n     * @return additionalPayeePrimaryRevenue_ amount of revenue to be sent to\n     * additional payee for primary sales\n     * @return additionalPayeePrimaryAddress_ address to send Artist's\n     * additional payee for primary sales revenue to. Will be null if no\n     * revenue is due to additional payee for primary sales (gas optimization).\n     * @dev this always returns four addresses and four revenues, but if the\n     * revenue is zero, the corresponding address will be address(0). It is up\n     * to the contract performing the revenue split to handle this\n     * appropriately.\n     */\n    function getPrimaryRevenueSplits(\n        uint256 _projectId,\n        uint256 _price\n    )\n        external\n        view\n        returns (\n            uint256 renderProviderRevenue_,\n            address payable renderProviderAddress_,\n            uint256 platformProviderRevenue_,\n            address payable platformProviderAddress_,\n            uint256 artistRevenue_,\n            address payable artistAddress_,\n            uint256 additionalPayeePrimaryRevenue_,\n            address payable additionalPayeePrimaryAddress_\n        )\n    {\n        ProjectFinance storage projectFinance = _projectIdToFinancials[\n            _projectId\n        ];\n        // calculate revenues – this is a three-way split between the\n        // render provider, the platform provider, and the artist, and\n        // is safe to perform this given that in the case of loss of\n        // precision Solidity will round down.\n        uint256 projectFunds = _price;\n        renderProviderRevenue_ =\n            (_price * uint256(_renderProviderPrimarySalesPercentage)) /\n            ONE_HUNDRED;\n        // renderProviderRevenue_ percentage is always <=100, so guaranteed to never underflow\n        projectFunds -= renderProviderRevenue_;\n        platformProviderRevenue_ =\n            (_price * uint256(_platformProviderPrimarySalesPercentage)) /\n            ONE_HUNDRED;\n        // platformProviderRevenue_ percentage is always <=100, so guaranteed to never underflow\n        projectFunds -= platformProviderRevenue_;\n        additionalPayeePrimaryRevenue_ =\n            (projectFunds *\n                projectFinance.additionalPayeePrimarySalesPercentage) /\n            ONE_HUNDRED;\n        // projectIdToAdditionalPayeePrimarySalesPercentage is always\n        // <=100, so guaranteed to never underflow\n        artistRevenue_ = projectFunds - additionalPayeePrimaryRevenue_;\n        // set addresses from storage\n        renderProviderAddress_ = renderProviderPrimarySalesAddress;\n        platformProviderAddress_ = platformProviderPrimarySalesAddress;\n        if (artistRevenue_ > 0) {\n            artistAddress_ = projectFinance.artistAddress;\n        }\n        if (additionalPayeePrimaryRevenue_ > 0) {\n            additionalPayeePrimaryAddress_ = projectFinance\n                .additionalPayeePrimarySales;\n        }\n    }\n\n    /**\n     * @notice Returns external asset dependency for project `_projectId` at index `_index`.\n     * If the dependencyType is ONCHAIN, the `data` field will contain the extrated bytecode data and `cid`\n     * will be an empty string. Conversly, for any other dependencyType, the `data` field will be an empty string\n     * and the `bytecodeAddress` will point to the zero address.\n     * If the dependencyType is ART_BLOCKS_DEPENDENCY_REGISTRY, the `cid` field will contain the string\n     * representation of the dependencyNameAndVersion bytes32 value stored in the dependency registry (\n     * at public address `artblocksDependencyRegistryAddress`)\n     * @param _projectId Project to be queried.\n     * @param _index Index of external asset dependency to be queried.\n     * @return ExternalAssetDependencyWithData External asset dependency for project `_projectId` at index `_index`.\n     */\n    function projectExternalAssetDependencyByIndex(\n        uint256 _projectId,\n        uint256 _index\n    ) external view returns (ExternalAssetDependencyWithData memory) {\n        return\n            V3FlexLib.projectExternalAssetDependencyByIndex({\n                _projectId: _projectId,\n                _index: _index,\n                _bytecodeStorageReaderContract: bytecodeStorageReaderContract\n            });\n    }\n\n    /**\n     * @notice Returns external asset dependency count for project `_projectId` at index `_index`.\n     * @param _projectId Project to be queried.\n     * @return uint256 Count of external asset dependencies for project `_projectId`.\n     */\n    function projectExternalAssetDependencyCount(\n        uint256 _projectId\n    ) external view returns (uint256) {\n        return\n            V3FlexLib.projectExternalAssetDependencyCount({\n                _projectId: _projectId\n            });\n    }\n\n    /**\n     * @notice Returns the preferred IPFS gateway for the platform.\n     * @return string Preferred IPFS gateway for the platform.\n     */\n    function preferredIPFSGateway() external view returns (string memory) {\n        return V3FlexLib.preferredIPFSGateway();\n    }\n\n    /**\n     * @notice Returns the preferred Arweave gateway for the platform.\n     * @return string Preferred Arweave gateway for the platform.\n     */\n    function preferredArweaveGateway() external view returns (string memory) {\n        return V3FlexLib.preferredArweaveGateway();\n    }\n\n    /**\n     * @notice Backwards-compatible (pre-V3) getter returning contract admin\n     * @return address Address of contract admin (same as owner)\n     */\n    function admin() external view returns (address) {\n        return owner();\n    }\n\n    /**\n     * @notice Gets the project ID for a given `_tokenId`.\n     * @param _tokenId Token ID to be queried.\n     * @return _projectId Project ID for given `_tokenId`.\n     */\n    function tokenIdToProjectId(\n        uint256 _tokenId\n    ) public pure returns (uint256 _projectId) {\n        return _tokenId / ONE_MILLION;\n    }\n\n    /**\n     * @notice Convenience function that returns whether `_sender` is allowed\n     * to call function with selector `_selector` on contract `_contract`, as\n     * determined by this contract's current Admin ACL contract. Expected use\n     * cases include minter contracts checking if caller is allowed to call\n     * admin-gated functions on minter contracts.\n     * @param _sender Address of the sender calling function with selector\n     * `_selector` on contract `_contract`.\n     * @param _contract Address of the contract being called by `_sender`.\n     * @param _selector Function selector of the function being called by\n     * `_sender`.\n     * @return bool Whether `_sender` is allowed to call function with selector\n     * `_selector` on contract `_contract`.\n     * @dev assumes the Admin ACL contract is the owner of this contract, which\n     * is expected to always be true.\n     * @dev adminACLContract is expected to either be null address (if owner\n     * has renounced ownership), or conform to IAdminACLV0 interface. Check for\n     * null address first to avoid revert when admin has renounced ownership.\n     */\n    function adminACLAllowed(\n        address _sender,\n        address _contract,\n        bytes4 _selector\n    ) public returns (bool) {\n        return\n            owner() != address(0) &&\n            adminACLContract.allowed(_sender, _contract, _selector);\n    }\n\n    /**\n     * @notice Returns contract owner. Set to deployer's address by default on\n     * contract deployment.\n     * @return address Address of contract owner.\n     * @dev ref: https://docs.openzeppelin.com/contracts/4.x/api/access#Ownable\n     * @dev owner role was called `admin` prior to V3 core contract\n     */\n    function owner()\n        public\n        view\n        override(Ownable, IGenArt721CoreContractV3_Base)\n        returns (address)\n    {\n        return Ownable.owner();\n    }\n\n    /**\n     * @notice Gets token URI for token ID `_tokenId`.\n     * @param _tokenId Token ID to be queried.\n     * @return string URI of token ID `_tokenId`.\n     * @dev token URIs are the concatenation of the project base URI and the\n     * token ID.\n     */\n    function tokenURI(\n        uint256 _tokenId\n    ) public view override returns (string memory) {\n        _onlyValidTokenId(_tokenId);\n        string memory _projectBaseURI = projects[tokenIdToProjectId(_tokenId)]\n            .projectBaseURI;\n        return string.concat(_projectBaseURI, _tokenId.toString());\n    }\n\n    /**\n     * @dev See {IERC165-supportsInterface}.\n     */\n    function supportsInterface(\n        bytes4 interfaceId\n    )\n        public\n        view\n        virtual\n        override(ERC721_PackedHashSeedV1, IERC165)\n        returns (bool)\n    {\n        return\n            interfaceId == _INTERFACE_ID_ERC2981 ||\n            super.supportsInterface(interfaceId);\n    }\n\n    /**\n     * @notice Forbids new projects from being created\n     * @dev only performs operation and emits event if contract is not already\n     * forbidding new projects.\n     */\n    function _forbidNewProjects() internal {\n        if (!newProjectsForbidden) {\n            newProjectsForbidden = true;\n            emit PlatformUpdated(\n                bytes32(\n                    uint256(PlatformUpdatedFields.FIELD_NEW_PROJECTS_FORBIDDEN)\n                )\n            );\n        }\n    }\n\n    /**\n     * @notice Transfers ownership of the contract to a new account (`newOwner`).\n     * Internal function without access restriction.\n     * @param newOwner New owner.\n     * @dev owner role was called `admin` prior to V3 core contract.\n     * @dev Overrides and wraps OpenZeppelin's _transferOwnership function to\n     * also update adminACLContract for improved introspection.\n     */\n    function _transferOwnership(address newOwner) internal override {\n        Ownable._transferOwnership(newOwner);\n        adminACLContract = IAdminACLV0(newOwner);\n    }\n\n    /**\n     * @notice Updates sales addresses for the platform and render providers to\n     * the input parameters.\n     * Reverts if invalid platform provider addresses are provided given the\n     * contract's immutably configured nullPlatformProvider state.\n     * Does not check render provider addresses in any way.\n     * @param _renderProviderPrimarySalesAddress Address of new primary sales\n     * payment address.\n     * @param _defaultRenderProviderSecondarySalesAddress Address of new secondary sales\n     * payment address.\n     * @param _platformProviderPrimarySalesAddress Address of new primary sales\n     * payment address.\n     * @param _defaultPlatformProviderSecondarySalesAddress Address of new secondary sales\n     * payment address.\n     */\n    function _updateProviderSalesAddresses(\n        address _renderProviderPrimarySalesAddress,\n        address _defaultRenderProviderSecondarySalesAddress,\n        address _platformProviderPrimarySalesAddress,\n        address _defaultPlatformProviderSecondarySalesAddress\n    ) internal {\n        if (nullPlatformProvider) {\n            // require null platform provider address\n            if (\n                _platformProviderPrimarySalesAddress != address(0) ||\n                _defaultPlatformProviderSecondarySalesAddress != address(0)\n            ) {\n                revert GenArt721Error(ErrorCodes.OnlyNullPlatformProvider);\n            }\n        } else {\n            _onlyNonZeroAddress(_platformProviderPrimarySalesAddress);\n            _onlyNonZeroAddress(_defaultPlatformProviderSecondarySalesAddress);\n        }\n        platformProviderPrimarySalesAddress = payable(\n            _platformProviderPrimarySalesAddress\n        );\n        defaultPlatformProviderSecondarySalesAddress = payable(\n            _defaultPlatformProviderSecondarySalesAddress\n        );\n        renderProviderPrimarySalesAddress = payable(\n            _renderProviderPrimarySalesAddress\n        );\n        defaultRenderProviderSecondarySalesAddress = payable(\n            _defaultRenderProviderSecondarySalesAddress\n        );\n        emit PlatformUpdated(\n            bytes32(\n                uint256(PlatformUpdatedFields.FIELD_PROVIDER_SALES_ADDRESSES)\n            )\n        );\n    }\n\n    /**\n     * @notice Updates minter address to `_minterAddress`.\n     * @param _minterAddress New minter address.\n     * @dev Note that this method does not check that the input address is\n     * not `address(0)`, as it is expected that callers of this method should\n     * perform input validation where applicable.\n     */\n    function _updateMinterContract(address _minterAddress) internal {\n        minterContract = _minterAddress;\n        emit MinterUpdated(_minterAddress);\n    }\n\n    /**\n     * @notice Updates randomizer address to `_randomizerAddress`.\n     * @param _randomizerAddress New randomizer address.\n     * @dev Note that this method does not check that the input address is\n     * not `address(0)`, as it is expected that callers of this method should\n     * perform input validation where applicable.\n     */\n    function _updateRandomizerAddress(address _randomizerAddress) internal {\n        randomizerContract = IRandomizer_V3CoreBase(_randomizerAddress);\n        // populate historical randomizer array\n        _historicalRandomizerAddresses.push(_randomizerAddress);\n        emit PlatformUpdated(\n            bytes32(uint256(PlatformUpdatedFields.FIELD_RANDOMIZER_ADDRESS))\n        );\n    }\n\n    /**\n     * @notice Updates split provider address to `_splitProviderAddress`.\n     * Reverts if `_splitProviderAddress` is the zero address.\n     * @param _splitProviderAddress New split provider address.\n     * @dev Note that this method does not check that the input address is\n     * not `address(0)`, as it is expected that callers of this method should\n     * perform input validation where applicable.\n     */\n    function _updateSplitProvider(address _splitProviderAddress) internal {\n        // require non-zero split provider address\n        _onlyNonZeroAddress(_splitProviderAddress);\n        splitProvider = ISplitProviderV0(_splitProviderAddress);\n        emit PlatformUpdated(\n            bytes32(uint256(PlatformUpdatedFields.FIELD_SPLIT_PROVIDER))\n        );\n    }\n\n    /**\n     * @notice Update the bytecode storage reader contract address, and emit corresponding event.\n     * @param _bytecodeStorageReaderContract New bytecode storage reader contract address.\n     */\n    function _updateBytecodeStorageReaderContract(\n        address _bytecodeStorageReaderContract\n    ) internal {\n        bytecodeStorageReaderContract = IBytecodeStorageReader_Base(\n            _bytecodeStorageReaderContract\n        );\n        emit PlatformUpdated(\n            bytes32(\n                uint256(PlatformUpdatedFields.FIELD_BYTECODE_STORAGE_READER)\n            )\n        );\n    }\n\n    /**\n     * @notice internal function to update a splitter contract for a project,\n     * based on the project's financials in this contract's storage.\n     * @dev Warning: this function uses storage reads to get the project's\n     * financials, so ensure storage has been updated before calling this\n     * @dev This function includes a trusted interaction that is entrusted to\n     * not reenter this contract.\n     * @param projectId Project ID to be updated.\n     */\n    function _assignSplitter(uint256 projectId) internal {\n        ProjectFinance storage projectFinance = _projectIdToFinancials[\n            projectId\n        ];\n        // assign project's royalty splitter\n        // @dev loads values from storage, so need to ensure storage has been updated\n        address royaltySplitter = splitProvider.getOrCreateSplitter(\n            ISplitProviderV0.SplitInputs({\n                platformProviderSecondarySalesAddress: projectFinance\n                    .platformProviderSecondarySalesAddress,\n                platformProviderSecondarySalesBPS: projectFinance\n                    .platformProviderSecondarySalesBPS,\n                renderProviderSecondarySalesAddress: projectFinance\n                    .renderProviderSecondarySalesAddress,\n                renderProviderSecondarySalesBPS: projectFinance\n                    .renderProviderSecondarySalesBPS,\n                artistTotalRoyaltyPercentage: projectFinance\n                    .secondaryMarketRoyaltyPercentage,\n                artist: projectFinance.artistAddress,\n                additionalPayee: projectFinance.additionalPayeeSecondarySales,\n                additionalPayeePercentage: projectFinance\n                    .additionalPayeeSecondarySalesPercentage\n            })\n        );\n\n        projectFinance.royaltySplitter = royaltySplitter;\n\n        emit ProjectRoyaltySplitterUpdated({\n            projectId: projectId,\n            royaltySplitter: royaltySplitter\n        });\n    }\n\n    /**\n     * @notice Updates default base URI to `_defaultBaseURI`.\n     * When new projects are added, their `projectBaseURI` is automatically\n     * initialized to `_defaultBaseURI`.\n     * @param _defaultBaseURI New default base URI.\n     * @dev Note that this method does not check that the input string is not\n     * the empty string, as it is expected that callers of this method should\n     * perform input validation where applicable.\n     */\n    function _updateDefaultBaseURI(string memory _defaultBaseURI) internal {\n        defaultBaseURI = _defaultBaseURI;\n        emit PlatformUpdated(\n            bytes32(uint256(PlatformUpdatedFields.FIELD_DEFAULT_BASE_URI))\n        );\n    }\n\n    /**\n     * @notice Internal function to complete a project.\n     * @param _projectId Project ID to be completed.\n     */\n    function _completeProject(uint256 _projectId) internal {\n        projects[_projectId].completedTimestamp = uint64(block.timestamp);\n        emit ProjectUpdated(\n            _projectId,\n            bytes32(uint256(ProjectUpdatedFields.FIELD_PROJECT_COMPLETED))\n        );\n    }\n\n    /**\n     * @notice Initializes the contract with the provided `engineConfiguration`.\n     * This function should be called atomically, immediately after deployment.\n     * Only callable once. Validation on `engineConfiguration` is performed by caller.\n     * @param engineConfiguration EngineConfiguration to configure the contract with.\n     * @param adminACLContract_ Address of admin access control contract, to be\n     * set as contract owner.\n     * @param defaultBaseURIHost Base URI prefix to initialize default base URI with.\n     * @param bytecodeStorageReaderContract_ Address of bytecode storage reader contract.\n     */\n    function _initialize(\n        EngineConfiguration memory engineConfiguration,\n        address adminACLContract_,\n        string memory defaultBaseURIHost,\n        address bytecodeStorageReaderContract_\n    ) internal {\n        // can only be initialized once\n        if (_initialized) {\n            revert GenArt721Error(ErrorCodes.ContractInitialized);\n        }\n        // immediately mark as initialized\n        _initialized = true;\n        // @dev assume renderProviderAddress, randomizer, and AdminACL non-zero\n        // checks on platform provider addresses performed in _updateProviderSalesAddresses\n        // initialize default sales revenue percentages and basis points\n        _renderProviderPrimarySalesPercentage = 10;\n        defaultRenderProviderSecondarySalesBPS = 250;\n        _platformProviderPrimarySalesPercentage = engineConfiguration\n            .nullPlatformProvider\n            ? 0\n            : 10;\n        defaultPlatformProviderSecondarySalesBPS = engineConfiguration\n            .nullPlatformProvider\n            ? 0\n            : 250;\n\n        // set token name and token symbol\n        ERC721_PackedHashSeedV1.initialize(\n            engineConfiguration.tokenName,\n            engineConfiguration.tokenSymbol\n        );\n        // update minter if populated\n        if (engineConfiguration.minterFilterAddress != address(0)) {\n            _updateMinterContract(engineConfiguration.minterFilterAddress);\n        }\n        _updateSplitProvider(engineConfiguration.splitProviderAddress);\n        _updateBytecodeStorageReaderContract(bytecodeStorageReaderContract_);\n        // setup immutable `autoApproveArtistSplitProposals` config\n        autoApproveArtistSplitProposals = engineConfiguration\n            .autoApproveArtistSplitProposals;\n        // setup immutable `nullPlatformProvider` config\n        nullPlatformProvider = engineConfiguration.nullPlatformProvider;\n        // setup immutable `allowArtistProjectActivation` config\n        allowArtistProjectActivation = engineConfiguration\n            .allowArtistProjectActivation;\n        // record contracts starting project ID\n        // casting-up is safe\n        startingProjectId = uint256(engineConfiguration.startingProjectId);\n        // @dev nullPlatformProvider must be set before calling _updateProviderSalesAddresses\n        _updateProviderSalesAddresses(\n            engineConfiguration.renderProviderAddress,\n            engineConfiguration.renderProviderAddress,\n            engineConfiguration.platformProviderAddress,\n            engineConfiguration.platformProviderAddress\n        );\n        _updateRandomizerAddress(engineConfiguration.randomizerContract);\n        // set AdminACL management contract as owner\n        _transferOwnership(adminACLContract_);\n        // initialize default base URI\n        _updateDefaultBaseURI(\n            string.concat(defaultBaseURIHost, address(this).toHexString(), \"/\")\n        );\n        // initialize next project ID\n        _nextProjectId = engineConfiguration.startingProjectId;\n        emit PlatformUpdated(\n            bytes32(uint256(PlatformUpdatedFields.FIELD_NEXT_PROJECT_ID))\n        );\n        // @dev This contract is registered on the core registry in a\n        // subsequent call by the factory.\n    }\n\n    /**\n     * @notice Internal function that returns whether a project is unlocked.\n     * Projects automatically lock four weeks after they are completed.\n     * Projects are considered completed when they have been invoked the\n     * maximum number of times.\n     * @param _projectId Project ID to be queried.\n     * @return bool true if project is unlocked, false otherwise.\n     * @dev This also enforces that the `_projectId` passed in is valid.\n     */\n    function _projectUnlocked(uint256 _projectId) internal view returns (bool) {\n        _onlyValidProjectId(_projectId);\n\n        uint256 projectCompletedTimestamp = projects[_projectId]\n            .completedTimestamp;\n        bool projectOpen = projectCompletedTimestamp == 0;\n        return\n            projectOpen ||\n            (block.timestamp - projectCompletedTimestamp <\n                FOUR_WEEKS_IN_SECONDS);\n    }\n\n    /**\n     * @notice Helper for calling bytecodeStorageReaderContract reader method;\n     * added for bytecode size reduction purposes.\n     */\n    function _readFromBytecode(\n        address _address\n    ) internal view returns (string memory) {\n        return bytecodeStorageReaderContract.readFromBytecode(_address);\n    }\n}\n"
      },
      "contracts/interfaces/v0.8.x/IAdminACLV0_Extended.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Created By: Art Blocks Inc.\n\npragma solidity ^0.8.0;\n\nimport \"./IAdminACLV0.sol\";\n\ninterface IAdminACLV0_Extended is IAdminACLV0 {\n    /**\n     * @notice Allows superAdmin change the superAdmin address.\n     * @param _newSuperAdmin The new superAdmin address.\n     * @param _genArt721CoreAddressesToUpdate Array of genArt721Core\n     * addresses to update to the new superAdmin, for indexing purposes only.\n     * @dev this function is gated to only superAdmin address.\n     */\n    function changeSuperAdmin(\n        address _newSuperAdmin,\n        address[] calldata _genArt721CoreAddressesToUpdate\n    ) external;\n}\n"
      },
      "contracts/interfaces/v0.8.x/IAdminACLV0.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Created By: Art Blocks Inc.\n\npragma solidity ^0.8.0;\n\ninterface IAdminACLV0 {\n    /**\n     * @notice Token ID `_tokenId` minted to `_to`.\n     * @param previousSuperAdmin The previous superAdmin address.\n     * @param newSuperAdmin The new superAdmin address.\n     * @param genArt721CoreAddressesToUpdate Array of genArt721Core\n     * addresses to update to the new superAdmin, for indexing purposes only.\n     */\n    event SuperAdminTransferred(\n        address indexed previousSuperAdmin,\n        address indexed newSuperAdmin,\n        address[] genArt721CoreAddressesToUpdate\n    );\n\n    /// Type of the Admin ACL contract, e.g. \"AdminACLV0\"\n    function AdminACLType() external view returns (string memory);\n\n    /// super admin address\n    function superAdmin() external view returns (address);\n\n    /**\n     * @notice Calls transferOwnership on other contract from this contract.\n     * This is useful for updating to a new AdminACL contract.\n     * @dev this function should be gated to only superAdmin-like addresses.\n     */\n    function transferOwnershipOn(\n        address _contract,\n        address _newAdminACL\n    ) external;\n\n    /**\n     * @notice Calls renounceOwnership on other contract from this contract.\n     * @dev this function should be gated to only superAdmin-like addresses.\n     */\n    function renounceOwnershipOn(address _contract) external;\n\n    /**\n     * @notice Checks if sender `_sender` is allowed to call function with selector\n     * `_selector` on contract `_contract`.\n     */\n    function allowed(\n        address _sender,\n        address _contract,\n        bytes4 _selector\n    ) external returns (bool);\n}\n"
      },
      "contracts/interfaces/v0.8.x/IBytecodeStorageReader_Base.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Creatd By: Art Blocks Inc.\n\npragma solidity ^0.8.0;\n\n/**\n * @title Art Blocks Script Storage Library - Minimal Interface for Reader Contracts\n * @notice This interface defines the minimal expected read function(s) for a Bytecode Storage Reader contract.\n */\ninterface IBytecodeStorageReader_Base {\n    /**\n     * @notice Read a string from a data contract deployed via BytecodeStorage.\n     * @dev may also support reading additional stored data formats in the future.\n     * @param address_ address of contract deployed via BytecodeStorage to be read\n     * @return data The string data stored at the specific address.\n     */\n    function readFromBytecode(\n        address address_\n    ) external view returns (string memory data);\n}\n"
      },
      "contracts/interfaces/v0.8.x/IDependencyRegistryCompatibleV0.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Created By: Art Blocks Inc.\npragma solidity ^0.8.19;\n\ninterface IDependencyRegistryCompatibleV0 {\n    /// Dependency registry managed by Art Blocks\n    function artblocksDependencyRegistryAddress()\n        external\n        view\n        returns (address);\n\n    /**\n     * @notice Returns script information for project `_projectId`.\n     * @param _projectId Project to be queried.\n     * @return scriptTypeAndVersion Project's script type and version\n     * (e.g. \"p5js(atSymbol)1.0.0\")\n     * @return aspectRatio Aspect ratio of project (e.g. \"1\" for square,\n     * \"1.77777778\" for 16:9, etc.)\n     * @return scriptCount Count of scripts for project\n     */\n    function projectScriptDetails(\n        uint256 _projectId\n    )\n        external\n        view\n        returns (\n            string memory scriptTypeAndVersion,\n            string memory aspectRatio,\n            uint256 scriptCount\n        );\n}\n"
      },
      "contracts/interfaces/v0.8.x/IGenArt721CoreContractExposesHashSeed.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Created By: Art Blocks Inc.\n\npragma solidity ^0.8.0;\n\ninterface IGenArt721CoreContractExposesHashSeed {\n    // function to read the hash-seed for a given tokenId\n    function tokenIdToHashSeed(\n        uint256 _tokenId\n    ) external view returns (bytes12);\n}\n"
      },
      "contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Created By: Art Blocks Inc.\n\npragma solidity ^0.8.0;\n\nimport \"./IAdminACLV0.sol\";\n\n/**\n * @title This interface is intended to house interface items that are common\n * across all GenArt721CoreContractV3 flagship and derivative implementations.\n * This interface extends the IManifold royalty interface in order to\n * add support the Royalty Registry by default.\n * @author Art Blocks Inc.\n */\ninterface IGenArt721CoreContractV3_Base {\n    // This interface emits generic events that contain fields that indicate\n    // which parameter has been updated. This is sufficient for application\n    // state management, while also simplifying the contract and indexing code.\n    // This was done as an alternative to having custom events that emit what\n    // field-values have changed for each event, given that changed values can\n    // be introspected by indexers due to the design of this smart contract\n    // exposing these state changes via publicly viewable fields.\n\n    /**\n     * @notice Project's royalty splitter was updated to `_splitter`.\n     * @dev New event in v3.2\n     * @param projectId The project ID.\n     * @param royaltySplitter The new splitter address to receive royalties.\n     */\n    event ProjectRoyaltySplitterUpdated(\n        uint256 indexed projectId,\n        address indexed royaltySplitter\n    );\n\n    // The following fields are used to indicate which contract-level parameter\n    // has been updated in the `PlatformUpdated` event:\n    // @dev only append to the end of this enum in the case of future updates\n    enum PlatformUpdatedFields {\n        FIELD_NEXT_PROJECT_ID, // 0\n        FIELD_NEW_PROJECTS_FORBIDDEN, // 1\n        FIELD_DEFAULT_BASE_URI, // 2\n        FIELD_RANDOMIZER_ADDRESS, // 3\n        FIELD_NEXT_CORE_CONTRACT, // 4\n        FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS, // 5\n        FIELD_ARTBLOCKS_ON_CHAIN_GENERATOR_ADDRESS, // 6\n        FIELD_PROVIDER_SALES_ADDRESSES, // 7\n        FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES, // 8\n        FIELD_PROVIDER_SECONDARY_SALES_BPS, // 9\n        FIELD_SPLIT_PROVIDER, // 10\n        FIELD_BYTECODE_STORAGE_READER // 11\n    }\n\n    // The following fields are used to indicate which project-level parameter\n    // has been updated in the `ProjectUpdated` event:\n    // @dev only append to the end of this enum in the case of future updates\n    enum ProjectUpdatedFields {\n        FIELD_PROJECT_COMPLETED, // 0\n        FIELD_PROJECT_ACTIVE, // 1\n        FIELD_PROJECT_ARTIST_ADDRESS, // 2\n        FIELD_PROJECT_PAUSED, // 3\n        FIELD_PROJECT_CREATED, // 4\n        FIELD_PROJECT_NAME, // 5\n        FIELD_PROJECT_ARTIST_NAME, // 6\n        FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE, // 7\n        FIELD_PROJECT_DESCRIPTION, // 8\n        FIELD_PROJECT_WEBSITE, // 9\n        FIELD_PROJECT_LICENSE, // 10\n        FIELD_PROJECT_MAX_INVOCATIONS, // 11\n        FIELD_PROJECT_SCRIPT, // 12\n        FIELD_PROJECT_SCRIPT_TYPE, // 13\n        FIELD_PROJECT_ASPECT_RATIO, // 14\n        FIELD_PROJECT_BASE_URI, // 15\n        FIELD_PROJECT_PROVIDER_SECONDARY_FINANCIALS // 16\n    }\n\n    /**\n     * @notice Error codes for the GenArt721 contract. Used by the GenArt721Error\n     * custom error.\n     * @dev only append to the end of this enum in the case of future updates\n     */\n    enum ErrorCodes {\n        OnlyNonZeroAddress, // 0\n        OnlyNonEmptyString, // 1\n        OnlyNonEmptyBytes, // 2\n        TokenDoesNotExist, // 3\n        ProjectDoesNotExist, // 4\n        OnlyUnlockedProjects, // 5\n        OnlyAdminACL, // 6\n        OnlyArtist, // 7\n        OnlyArtistOrAdminACL, // 8\n        OnlyAdminACLOrRenouncedArtist, // 9\n        OnlyMinterContract, // 10\n        MaxInvocationsReached, // 11\n        ProjectMustExistAndBeActive, // 12\n        PurchasesPaused, // 13\n        OnlyRandomizer, // 14\n        TokenHashAlreadySet, // 15\n        NoZeroHashSeed, // 16\n        OverMaxSumOfPercentages, // 17\n        IndexOutOfBounds, // 18\n        OverMaxSumOfBPS, // 19\n        MaxOf100Percent, // 20\n        PrimaryPayeeIsZeroAddress, // 21\n        SecondaryPayeeIsZeroAddress, // 22\n        MustMatchArtistProposal, // 23\n        NewProjectsForbidden, // 24\n        NewProjectsAlreadyForbidden, // 25\n        OnlyArtistOrAdminIfLocked, // 26\n        OverMaxSecondaryRoyaltyPercentage, // 27\n        OnlyMaxInvocationsDecrease, // 28\n        OnlyGteInvocations, // 29\n        ScriptIdOutOfRange, // 30\n        NoScriptsToRemove, // 31\n        ScriptTypeAndVersionFormat, // 32\n        AspectRatioTooLong, // 33\n        AspectRatioNoNumbers, // 34\n        AspectRatioImproperFormat, // 35\n        OnlyNullPlatformProvider, // 36\n        ContractInitialized // 37\n    }\n\n    /**\n     * @notice Emits an error code `_errorCode` in the GenArt721Error event.\n     * @dev Emitting error codes instead of error strings saves significant\n     * contract bytecode size, allowing for more contract functionality within\n     * the 24KB contract size limit.\n     * @param _errorCode The error code to emit. See ErrorCodes enum.\n     */\n    error GenArt721Error(ErrorCodes _errorCode);\n\n    /**\n     * @notice Token ID `_tokenId` minted to `_to`.\n     */\n    event Mint(address indexed _to, uint256 indexed _tokenId);\n\n    /**\n     * @notice currentMinter updated to `_currentMinter`.\n     * @dev Implemented starting with V3 core\n     */\n    event MinterUpdated(address indexed _currentMinter);\n\n    /**\n     * @notice Platform updated on bytes32-encoded field `_field`.\n     */\n    event PlatformUpdated(bytes32 indexed _field);\n\n    /**\n     * @notice Project ID `_projectId` updated on bytes32-encoded field\n     * `_update`.\n     */\n    event ProjectUpdated(uint256 indexed _projectId, bytes32 indexed _update);\n\n    event ProposedArtistAddressesAndSplits(\n        uint256 indexed _projectId,\n        address _artistAddress,\n        address _additionalPayeePrimarySales,\n        uint256 _additionalPayeePrimarySalesPercentage,\n        address _additionalPayeeSecondarySales,\n        uint256 _additionalPayeeSecondarySalesPercentage\n    );\n\n    event AcceptedArtistAddressesAndSplits(uint256 indexed _projectId);\n\n    // version and type of the core contract\n    // coreVersion is a string of the form \"0.x.y\"\n    function coreVersion() external view returns (string memory);\n\n    // coreType is a string of the form \"GenArt721CoreV3\"\n    function coreType() external view returns (string memory);\n\n    // owner (pre-V3 was named admin) of contract\n    // this is expected to be an Admin ACL contract for V3\n    function owner() external view returns (address);\n\n    // Admin ACL contract for V3, will be at the address owner()\n    function adminACLContract() external returns (IAdminACLV0);\n\n    // backwards-compatible (pre-V3) admin - equal to owner()\n    function admin() external view returns (address);\n\n    /**\n     * Function determining if _sender is allowed to call function with\n     * selector _selector on contract `_contract`. Intended to be used with\n     * peripheral contracts such as minters, as well as internally by the\n     * core contract itself.\n     */\n    function adminACLAllowed(\n        address _sender,\n        address _contract,\n        bytes4 _selector\n    ) external returns (bool);\n\n    /// getter function of public variable\n    function startingProjectId() external view returns (uint256);\n\n    // getter function of public variable\n    function nextProjectId() external view returns (uint256);\n\n    // getter function of public mapping\n    function tokenIdToProjectId(\n        uint256 tokenId\n    ) external view returns (uint256 projectId);\n\n    // @dev this is not available in V0\n    function isMintWhitelisted(address minter) external view returns (bool);\n\n    function projectIdToArtistAddress(\n        uint256 _projectId\n    ) external view returns (address payable);\n\n    function projectIdToSecondaryMarketRoyaltyPercentage(\n        uint256 _projectId\n    ) external view returns (uint256);\n\n    function projectURIInfo(\n        uint256 _projectId\n    ) external view returns (string memory projectBaseURI);\n\n    // @dev new function in V3\n    function projectStateData(\n        uint256 _projectId\n    )\n        external\n        view\n        returns (\n            uint256 invocations,\n            uint256 maxInvocations,\n            bool active,\n            bool paused,\n            uint256 completedTimestamp,\n            bool locked\n        );\n\n    function projectDetails(\n        uint256 _projectId\n    )\n        external\n        view\n        returns (\n            string memory projectName,\n            string memory artist,\n            string memory description,\n            string memory website,\n            string memory license\n        );\n\n    function projectScriptDetails(\n        uint256 _projectId\n    )\n        external\n        view\n        returns (\n            string memory scriptTypeAndVersion,\n            string memory aspectRatio,\n            uint256 scriptCount\n        );\n\n    function projectScriptByIndex(\n        uint256 _projectId,\n        uint256 _index\n    ) external view returns (string memory);\n\n    function tokenIdToHash(uint256 _tokenId) external view returns (bytes32);\n\n    // function to set a token's hash (must be guarded)\n    function setTokenHash_8PT(uint256 _tokenId, bytes32 _hash) external;\n\n    // @dev gas-optimized signature in V3 for `mint`\n    function mint_Ecf(\n        address _to,\n        uint256 _projectId,\n        address _by\n    ) external returns (uint256 tokenId);\n}\n"
      },
      "contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine_Flex.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Created By: Art Blocks Inc.\n\npragma solidity ^0.8.0;\n\nimport \"./IAdminACLV0.sol\";\nimport \"./IGenArt721CoreContractV3_Engine.sol\";\n\n/**\n * @title This interface is intended to house interface items that are common\n * across all GenArt721CoreContractV3 Engine Flex and derivative implementations.\n * @author Art Blocks Inc.\n */\ninterface IGenArt721CoreContractV3_Engine_Flex is\n    IGenArt721CoreContractV3_Engine\n{\n    /**\n     * @notice When an external asset dependency is updated or added, this event is emitted.\n     * @param _projectId The project ID of the project that was updated.\n     * @param _index The index of the external asset dependency that was updated.\n     * @param _cid Field that contains the CID of the dependency if IPFS or ARWEAVE, empty string of ONCHAIN, or a string representation\n     * of the Art Blocks Dependency Registry's `dependencyNameAndVersion` if ART_BLOCKS_DEPENDENCY_REGISTRY.\n     * @param _dependencyType The type of the external asset dependency.\n     * @param _externalAssetDependencyCount The number of external asset dependencies.\n     */\n    event ExternalAssetDependencyUpdated(\n        uint256 indexed _projectId,\n        uint256 indexed _index,\n        string _cid,\n        ExternalAssetDependencyType _dependencyType,\n        uint24 _externalAssetDependencyCount\n    );\n\n    /**\n     * @notice The project id `_projectId` has had an external asset dependency removed at index `_index`.\n     */\n    event ExternalAssetDependencyRemoved(\n        uint256 indexed _projectId,\n        uint256 indexed _index\n    );\n\n    /**\n     * @notice The preferred gateway for dependency type `_dependencyType` has been updated to `_gatewayAddress`.\n     */\n    event GatewayUpdated(\n        ExternalAssetDependencyType indexed _dependencyType,\n        string _gatewayAddress\n    );\n\n    /**\n     * @notice The project id `_projectId` has had all external asset dependencies locked.\n     * @dev This is a one-way operation. Once locked, the external asset dependencies cannot be updated.\n     */\n    event ProjectExternalAssetDependenciesLocked(uint256 indexed _projectId);\n\n    /**\n     * @notice An external asset dependency type. Can be one of IPFS, ARWEAVE, ONCHAIN, or ART_BLOCKS_DEPENDENCY_REGISTRY.\n     */\n    enum ExternalAssetDependencyType {\n        IPFS,\n        ARWEAVE,\n        ONCHAIN,\n        ART_BLOCKS_DEPENDENCY_REGISTRY\n    }\n\n    /**\n     * @notice Project storage that relate to Flex data.\n     */\n    struct ProjectFlex {\n        bool externalAssetDependenciesLocked;\n        uint24 externalAssetDependencyCount;\n        mapping(uint256 => ExternalAssetDependency) externalAssetDependencies;\n    }\n\n    /**\n     * @notice An external asset dependency, without the retrieved data of any ONCHAIN assets. This reflects what is\n     * stored in this contract's storage.\n     * @param CID field that contains the CID of the dependency if IPFS or ARWEAVE, empty string of ONCHAIN, or a string representation\n     * of the Art Blocks Dependency Registry's `dependencyNameAndVersion` if ART_BLOCKS_DEPENDENCY_REGISTRY.\n     * @param dependencyType field that contains the type of the dependency.\n     * @param bytecodeAddress field that contains the address of the bytecode for this dependency if ONCHAIN, null address otherwise.\n     */\n    struct ExternalAssetDependency {\n        string cid;\n        ExternalAssetDependencyType dependencyType;\n        address bytecodeAddress;\n    }\n\n    /**\n     * @notice An external asset dependency with data. This is a convenience struct.\n     * @param CID field that contains the CID of the dependency if IPFS or ARWEAVE, empty string of ONCHAIN, or a string representation\n     * of the Art Blocks Dependency Registry's `dependencyNameAndVersion` if ART_BLOCKS_DEPENDENCY_REGISTRY.\n     * @param dependencyType field that contains the type of the dependency.\n     * @param bytecodeAddress field that contains the address of the bytecode for this dependency if ONCHAIN, null address otherwise.\n     * @param data field that contains the data retrieved from this bytecode address if ONCHAIN, empty string otherwise.\n     */\n    struct ExternalAssetDependencyWithData {\n        string cid;\n        ExternalAssetDependencyType dependencyType;\n        address bytecodeAddress;\n        string data;\n    }\n\n    // preferredIPFSGateway is a url string\n    function preferredIPFSGateway() external view returns (string memory);\n\n    // preferredArweaveGateway is a url string\n    function preferredArweaveGateway() external view returns (string memory);\n\n    // updates the preferred IPFS gateway\n    function updateIPFSGateway(string calldata _gateway) external;\n\n    // updates the preferred Arweave gateway\n    function updateArweaveGateway(string calldata _gateway) external;\n\n    // locks the external asset dependencies for a project\n    function lockProjectExternalAssetDependencies(uint256 _projectId) external;\n\n    // updates the external asset dependency for a project at a given index\n    function updateProjectExternalAssetDependency(\n        uint256 _projectId,\n        uint256 _index,\n        string memory _cidOrData,\n        ExternalAssetDependencyType _dependencyType\n    ) external;\n\n    // adds an external asset dependency for a project\n    function addProjectExternalAssetDependency(\n        uint256 _projectId,\n        string memory _cidOrData,\n        ExternalAssetDependencyType _dependencyType\n    ) external;\n\n    // removes an external asset dependency for a project at a given index\n    function removeProjectExternalAssetDependency(\n        uint256 _projectId,\n        uint256 _index\n    ) external;\n\n    // getter function for project external asset dependencies\n    function projectExternalAssetDependencyByIndex(\n        uint256 _projectId,\n        uint256 _index\n    ) external view returns (ExternalAssetDependencyWithData memory);\n\n    // getter function project external asset dependency count\n    function projectExternalAssetDependencyCount(\n        uint256 _projectId\n    ) external view returns (uint256);\n}\n"
      },
      "contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_Engine.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Created By: Art Blocks Inc.\n\npragma solidity ^0.8.0;\n\nimport \"./IAdminACLV0.sol\";\nimport \"./IGenArt721CoreContractV3_Base.sol\";\nimport \"./ISplitProviderV0.sol\";\n\n/**\n * @notice Struct representing Engine contract configuration.\n * @param tokenName Name of token.\n * @param tokenSymbol Token symbol.\n * @param renderProviderAddress address to send render provider revenue to\n * @param randomizerContract Randomizer contract.\n * @param splitProviderAddress Address to use as royalty splitter provider for the contract.\n * @param minterFilterAddress Address of the Minter Filter to set as the Minter\n * on the contract.\n * @param startingProjectId The initial next project ID.\n * @param autoApproveArtistSplitProposals Whether or not to always\n * auto-approve proposed artist split updates.\n * @param nullPlatformProvider Enforce always setting zero platform provider fees and addresses.\n * @param allowArtistProjectActivation Allow artist to activate their own projects.\n * @dev _startingProjectId should be set to a value much, much less than\n * max(uint248), but an explicit input type of `uint248` is used as it is\n * safer to cast up to `uint256` than it is to cast down for the purposes\n * of setting `_nextProjectId`.\n */\nstruct EngineConfiguration {\n    string tokenName;\n    string tokenSymbol;\n    address renderProviderAddress;\n    address platformProviderAddress;\n    address newSuperAdminAddress;\n    address randomizerContract;\n    address splitProviderAddress;\n    address minterFilterAddress;\n    uint248 startingProjectId;\n    bool autoApproveArtistSplitProposals;\n    bool nullPlatformProvider;\n    bool allowArtistProjectActivation;\n}\n\ninterface IGenArt721CoreContractV3_Engine is IGenArt721CoreContractV3_Base {\n    // @dev new function in V3.2\n    /**\n     * @notice Initializes the contract with the provided `engineConfiguration`.\n     * This function should be called atomically, immediately after deployment.\n     * Only callable once. Validation on `engineConfiguration` is performed by caller.\n     * @param engineConfiguration EngineConfiguration to configure the contract with.\n     * @param adminACLContract_ Address of admin access control contract, to be\n     * set as contract owner.\n     * @param defaultBaseURIHost Base URI prefix to initialize default base URI with.\n     * @param bytecodeStorageReaderContract_ Address of the bytecode storage reader contract.\n     */\n    function initialize(\n        EngineConfiguration calldata engineConfiguration,\n        address adminACLContract_,\n        string memory defaultBaseURIHost,\n        address bytecodeStorageReaderContract_\n    ) external;\n\n    // @dev new function in V3\n    function getPrimaryRevenueSplits(\n        uint256 _projectId,\n        uint256 _price\n    )\n        external\n        view\n        returns (\n            uint256 renderProviderRevenue_,\n            address payable renderProviderAddress_,\n            uint256 platformProviderRevenue_,\n            address payable platformProviderAddress_,\n            uint256 artistRevenue_,\n            address payable artistAddress_,\n            uint256 additionalPayeePrimaryRevenue_,\n            address payable additionalPayeePrimaryAddress_\n        );\n\n    // @dev The render provider primary sales payment address\n    function renderProviderPrimarySalesAddress()\n        external\n        view\n        returns (address payable);\n\n    // @dev The platform provider primary sales payment address\n    function platformProviderPrimarySalesAddress()\n        external\n        view\n        returns (address payable);\n\n    // @dev Percentage of primary sales allocated to the render provider\n    function renderProviderPrimarySalesPercentage()\n        external\n        view\n        returns (uint256);\n\n    // @dev Percentage of primary sales allocated to the platform provider\n    function platformProviderPrimarySalesPercentage()\n        external\n        view\n        returns (uint256);\n\n    /** @notice The default render provider payment address for all secondary sales royalty\n     * revenues, for all new projects. Individual project payment info is defined\n     * in each project's ProjectFinance struct.\n     * @return The default render provider payment address for secondary sales royalties.\n     */\n    function defaultRenderProviderSecondarySalesAddress()\n        external\n        view\n        returns (address payable);\n\n    /** @notice The default platform provider payment address for all secondary sales royalty\n     * revenues, for all new projects. Individual project payment info is defined\n     * in each project's ProjectFinance struct.\n     * @return The default platform provider payment address for secondary sales royalties.\n     */\n    function defaultPlatformProviderSecondarySalesAddress()\n        external\n        view\n        returns (address payable);\n\n    /** @notice The default render provider payment basis points for all secondary sales royalty\n     * revenues, for all new projects. Individual project payment info is defined\n     * in each project's ProjectFinance struct.\n     * @return The default render provider payment basis points for secondary sales royalties.\n     */\n    function defaultRenderProviderSecondarySalesBPS()\n        external\n        view\n        returns (uint256);\n\n    /** @notice The default platform provider payment basis points for all secondary sales royalty\n     * revenues, for all new projects. Individual project payment info is defined\n     * in each project's ProjectFinance struct.\n     * @return The default platform provider payment basis points for secondary sales royalties.\n     */\n    function defaultPlatformProviderSecondarySalesBPS()\n        external\n        view\n        returns (uint256);\n\n    /**\n     * @notice The address of the current split provider being used by the contract.\n     * @return The address of the current split provider.\n     */\n    function splitProvider() external view returns (ISplitProviderV0);\n}\n"
      },
      "contracts/interfaces/v0.8.x/IGenArt721CoreContractV3_ProjectFinance.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Created By: Art Blocks Inc.\n\npragma solidity ^0.8.0;\n\nimport \"./IAdminACLV0.sol\";\n\n/**\n * @title This interface defines a project finance struct that is used in the\n * GenArt721CoreContractV3 flagship and derivative implementations beginning\n * with v3.2.0. This struct is intended to house all financial information\n * related to a project, including royalties, artist splits, and platform\n * provider splits.\n * @author Art Blocks Inc.\n */\ninterface IGenArt721CoreContractV3_ProjectFinance {\n    /// packed struct containing project financial information\n    struct ProjectFinance {\n        address payable additionalPayeePrimarySales;\n        // packed uint: max of 95, max uint8 = 255\n        uint8 secondaryMarketRoyaltyPercentage;\n        address payable additionalPayeeSecondarySales;\n        // packed uint: max of 100, max uint8 = 255\n        uint8 additionalPayeeSecondarySalesPercentage;\n        address payable artistAddress;\n        // packed uint: max of 100, max uint8 = 255\n        uint8 additionalPayeePrimarySalesPercentage;\n        address platformProviderSecondarySalesAddress;\n        // packed uint: max of 10_000 max uint16 = 65_535\n        uint16 platformProviderSecondarySalesBPS;\n        address renderProviderSecondarySalesAddress;\n        // packed uint: max of 10_000 max uint16 = 65_535\n        uint16 renderProviderSecondarySalesBPS;\n        // address to send ERC-2981 royalties to\n        address royaltySplitter;\n    }\n\n    /**\n     * @notice View function returning project financial details for project\n     * `_projectId`.\n     * @param _projectId Project ID to be queried.\n     * @return ProjectFinance Project financial details.\n     */\n    function projectIdToFinancials(\n        uint256 _projectId\n    ) external view returns (ProjectFinance memory);\n}\n"
      },
      "contracts/interfaces/v0.8.x/integration-refs/splits-0x-v2/ISplitFactoryV2.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Created By: Art Blocks Inc. to support the 0xSplits V2 integration\n// Sourced from:\n//  - https://github.com/0xSplits/splits-contracts-monorepo/blob/main/packages/splits-v2/src/libraries/SplitV2.sol\n//  - https://github.com/0xSplits/splits-contracts-monorepo/blob/main/packages/splits-v2/src/splitters/SplitFactoryV2.sol\n\npragma solidity ^0.8.0;\n\ninterface ISplitFactoryV2 {\n    /* -------------------------------------------------------------------------- */\n    /*                                   STRUCTS                                  */\n    /* -------------------------------------------------------------------------- */\n\n    /**\n     * @notice Split struct\n     * @dev This struct is used to store the split information.\n     * @dev There are no hard caps on the number of recipients/totalAllocation/allocation unit. Thus the chain and its\n     * gas limits will dictate these hard caps. Please double check if the split you are creating can be distributed on\n     * the chain.\n     * @param recipients The recipients of the split.\n     * @param allocations The allocations of the split.\n     * @param totalAllocation The total allocation of the split.\n     * @param distributionIncentive The incentive for distribution. Limits max incentive to 6.5%.\n     */\n    struct Split {\n        address[] recipients;\n        uint256[] allocations;\n        uint256 totalAllocation;\n        uint16 distributionIncentive;\n    }\n\n    /* -------------------------------------------------------------------------- */\n    /*                                 FUNCTIONS                                  */\n    /* -------------------------------------------------------------------------- */\n\n    /**\n     * @notice Create a new split with params and owner.\n     * @param _splitParams Params to create split with.\n     * @param _owner Owner of created split.\n     * @param _creator Creator of created split.\n     * @param _salt Salt for create2.\n     * @return split Address of the created split.\n     */\n    function createSplitDeterministic(\n        Split calldata _splitParams,\n        address _owner,\n        address _creator,\n        bytes32 _salt\n    ) external returns (address split);\n\n    /**\n     * @notice Predict the address of a new split and check if it is deployed.\n     * @param _splitParams Params to create split with.\n     * @param _owner Owner of created split.\n     * @param _salt Salt for create2.\n     */\n    function isDeployed(\n        Split calldata _splitParams,\n        address _owner,\n        bytes32 _salt\n    ) external view returns (address split, bool exists);\n}\n"
      },
      "contracts/interfaces/v0.8.x/IRandomizer_V3CoreBase.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Creatd By: Art Blocks Inc.\n\npragma solidity ^0.8.0;\n\ninterface IRandomizer_V3CoreBase {\n    /**\n     * @notice This function is intended to be called by a core contract, and\n     * the core contract can be assured that the randomizer will call back to\n     * the calling contract to set the token hash seed for `_tokenId` via\n     * `setTokenHash_8PT`.\n     * @dev This function may revert if hash seed generation is improperly\n     * configured (for example, if in polyptych mode, but no hash seed has been\n     * previously configured).\n     * @dev This function is not specifically gated to any specific caller, but\n     * will only call back to the calling contract, `msg.sender`, to set the\n     * specified token's hash seed.\n     * A third party contract calling this function will not be able to set the\n     * token hash seed on a different core contract.\n     * @param _tokenId The token ID must be assigned a hash.\n     */\n    function assignTokenHash(uint256 _tokenId) external;\n}\n"
      },
      "contracts/interfaces/v0.8.x/ISplitProviderV0.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Creatd By: Art Blocks Inc.\n\npragma solidity ^0.8.0;\n\nimport {ISplitFactoryV2} from \"./integration-refs/splits-0x-v2/ISplitFactoryV2.sol\";\n\ninterface ISplitProviderV0 {\n    /**\n     * @notice SplitInputs struct defines the inputs for requested splitters.\n     * It is defined in a way easily communicated from the Art Blocks GenArt721V3 contract,\n     * to allow for easy integration and minimal additional bytecode in the GenArt721V3 contract.\n     */\n    struct SplitInputs {\n        address platformProviderSecondarySalesAddress;\n        uint16 platformProviderSecondarySalesBPS;\n        address renderProviderSecondarySalesAddress;\n        uint16 renderProviderSecondarySalesBPS;\n        uint8 artistTotalRoyaltyPercentage;\n        address artist;\n        address additionalPayee;\n        uint8 additionalPayeePercentage;\n    }\n\n    /**\n     * @notice Emitted when a new splitter contract is created.\n     * @param splitter address of the splitter contract\n     */\n    event SplitterCreated(address indexed splitter);\n\n    /**\n     * @notice Gets or creates an immutable splitter contract at a deterministic address.\n     * Splits in the splitter contract are determined by the input split parameters,\n     * so we can safely create the splitter contract at a deterministic address (or use\n     * the existing splitter contract if it already exists at that address).\n     * @dev Uses the 0xSplits v2 implementation to create a splitter contract\n     * @param splitInputs The split input parameters.\n     * @return splitter The newly created splitter contract address.\n     */\n    function getOrCreateSplitter(\n        SplitInputs calldata splitInputs\n    ) external returns (address);\n\n    /**\n     * @notice Indicates the type of the contract, e.g. `SplitProviderV0`.\n     * @return type_ The type of the contract.\n     */\n    function type_() external pure returns (bytes32);\n}\n"
      },
      "contracts/libs/v0.8.x/BytecodeStorageV2.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Created By: Art Blocks Inc.\n\nimport {LibZip} from \"solady/src/utils/LibZip.sol\";\n\npragma solidity ^0.8.0;\n\n/**\n * @title Art Blocks Script Storage Library\n * @notice Utilize contract bytecode as persistent storage for large chunks of script string data.\n *         V2 includes optional on-chain compression/decompression via solady LibZip.\n *         This library is intended to have an external deployed copy that is released in the future,\n *         and, as such, has been designed to support both updated V2 and V1 (versioned, with purging removed)\n *         reads as well as backwards-compatible reads for both a) the unversioned \"V0\" storage contracts\n *         which were deployed by the original version of this libary and b) contracts that were deployed\n *         using one of the SSTORE2 implementations referenced below.\n *         For these pre-V1 storage contracts (which themselves did not have any explicit versioning semantics)\n *         backwards-compatible reads are optimistic, and only expected to work for contracts actually\n *         deployed by the original version of this library – and may fail ungracefully if attempted to be\n *         used to read from other contracts.\n *         This library is split into two components, intended to be updated in tandem, and thus included\n *         here in the same source file. One component is an internal library that is intended to be embedded\n *         directly into other contracts and provides all _write_ functionality. The other is a public library\n *         that is intended to be deployed as a standalone contract and provides all _read_ functionality.\n *\n * @author Art Blocks Inc.\n * @author Modified from 0xSequence (https://github.com/0xsequence/sstore2/blob/master/contracts/SSTORE2.sol)\n * @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)\n * @author Utilizes LibZip from solady (https://github.com/Vectorized/solady/blob/main/src/utils/LibZip.sol)\n *\n * @dev Compared to the above two rerferenced libraries, this contracts-as-storage implementation makes a few\n *      notably different design decisions:\n *      - uses the `string` data type for input/output on reads, rather than speaking in bytes directly,\n *        with an exception for optionally compressed data which are input as bytes\n *      - stores the \"writer\" address (library user) in the deployed contract bytes, which is useful for\n *        on-chain introspection and provenance purposes\n *      - stores a very simple versioning string in the deployed contract bytes, which captures the version\n *        of the library that was used to deploy the storage contract and useful for supporting future\n *        compatibility management as this library evolves (e.g. in response to EOF v1 migration plans)\n *      - stores a bool indicating if the stored data are compressed.\n *      Also, given that much of this library is written in assembly, this library makes use of a slightly\n *      different convention (when compared to the rest of the Art Blocks smart contract repo) around\n *      pre-defining return values in some cases in order to simplify need to directly memory manage these\n *      return values.\n */\n\n/**\n * @title Art Blocks Script Storage Library (Public, Reads)\n * @author Art Blocks Inc.\n * @notice The public library for reading from storage contracts. This library is intended to be deployed as a\n *         standalone contract, and provides all _read_ functionality.\n */\nlibrary BytecodeStorageReader {\n    // Define the set of known valid version strings that may be stored in the deployed storage contract bytecode\n    // note: These are all intentionally exactly 32-bytes and are null-terminated. Null-termination is used due\n    //       to this being the standard expected formatting in common web3 tooling such as ethers.js. Please see\n    //       the following for additional context: https://docs.ethers.org/v5/api/utils/strings/#Bytes32String\n    // Used for storage contracts that were deployed by an unknown source\n    bytes32 public constant UNKNOWN_VERSION_STRING =\n        \"UNKNOWN_VERSION_STRING_________ \";\n    // Pre-dates versioning string, so this doesn't actually exist in any deployed contracts,\n    // but is useful for backwards-compatible semantics with original version of this library\n    bytes32 public constant V0_VERSION_STRING =\n        \"BytecodeStorage_V0.0.0_________ \";\n    // The first versioned storage contract, deployed by an updated version of this library\n    bytes32 public constant V1_VERSION_STRING =\n        \"BytecodeStorage_V1.0.0_________ \";\n    // The first versioned storage contract, deployed by an updated version of this library\n    bytes32 public constant V2_VERSION_STRING =\n        \"BytecodeStorage_V2.0.0_________ \";\n    // The current version of this library.\n    bytes32 public constant CURRENT_VERSION = V2_VERSION_STRING;\n\n    //---------------------------------------------------------------------------------------------------------------//\n    // Starting Index | Size | Ending Index | Description                                                            //\n    //---------------------------------------------------------------------------------------------------------------//\n    // 0              | N/A  | 0            |                                                                        //\n    // 0              | 1    | 1            | single byte opcode for making the storage contract non-executable      //\n    // 1              | 32   | 33           | the 32 byte slot used for storing a basic versioning string            //\n    // 33             | 32   | 65           | the 32 bytes for storing the deploying contract's (0-padded) address   //\n    // 65             | 1    | 66           | single byte indicating if the stored data are compressed               //\n    //---------------------------------------------------------------------------------------------------------------//\n    // Define the offset for where the \"meta bytes\" end, and the \"data bytes\" begin. Note that this is a manually\n    // calculated value, and must be updated if the above table is changed. It is expected that tests will fail\n    // loudly if these values are not updated in-step with eachother.\n    uint256 private constant VERSION_OFFSET = 1;\n    uint256 private constant ADDRESS_OFFSET = 33;\n    uint256 private constant COMPRESSION_OFFSET = 65;\n    uint256 private constant DATA_OFFSET = 66;\n\n    // Define the set of known *historic* offset values for where the \"meta bytes\" end, and the \"data bytes\" begin.\n    // SSTORE2 deployed storage contracts take the general format of:\n    // concat(0x00, data)\n    // note: this is true for both variants of the SSTORE2 library\n    uint256 private constant SSTORE2_DATA_OFFSET = 1;\n    // V0 deployed storage contracts take the general format of:\n    // concat(gated-cleanup-logic, deployer-address, data)\n    uint256 private constant V0_ADDRESS_OFFSET = 72;\n    uint256 private constant V0_DATA_OFFSET = 104;\n    // V1 deployed storage contracts take the general format of:\n    // concat(invalid opcode, version, deployer-address, data)\n    uint256 private constant V1_ADDRESS_OFFSET = 33;\n    uint256 private constant V1_DATA_OFFSET = 65;\n    // V2 deployed storage contracts take the general format of:\n    // concat(invalid opcode, version, deployer-address, compression-bool, data)\n    uint256 private constant V2_ADDRESS_OFFSET = ADDRESS_OFFSET;\n    uint256 private constant V2_DATA_OFFSET = DATA_OFFSET;\n\n    /*//////////////////////////////////////////////////////////////\n                               READ LOGIC\n    //////////////////////////////////////////////////////////////*/\n\n    /**\n     * @notice Read a string from contract bytecode\n     * @param _address address of deployed contract with bytecode stored in the V0 or V1 or V2 format\n     * @return data string read from contract bytecode\n     * @dev This function performs input validation that the contract to read is in an expected format\n     */\n    function readFromBytecode(\n        address _address\n    ) public view returns (string memory data) {\n        (\n            uint256 dataOffset,\n            bool isCompressed\n        ) = _bytecodeDataOffsetAndIsCompressedAt(_address);\n        if (isCompressed) {\n            return\n                string(\n                    LibZip.flzDecompress(\n                        readBytesFromBytecode(_address, dataOffset)\n                    )\n                );\n        } else {\n            return string(readBytesFromBytecode(_address, dataOffset));\n        }\n    }\n\n    /**\n     * @notice Read the bytes from contract bytecode that was written to the EVM using SSTORE2\n     * @param _address address of deployed contract with bytecode stored in the SSTORE2 format\n     * @return data bytes read from contract bytecode\n     * @dev This function performs no input validation on the provided contract,\n     *      other than that there is content to read (but not that its a \"storage contract\")\n     */\n    function readBytesFromSSTORE2Bytecode(\n        address _address\n    ) public view returns (bytes memory data) {\n        return readBytesFromBytecode(_address, SSTORE2_DATA_OFFSET);\n    }\n\n    /**\n     * @notice Read the bytes from contract bytecode, with an explicitly provided starting offset\n     * @param _address address of deployed contract with bytecode stored in the V0 or V1 format\n     * @param _offset offset to read from in contract bytecode, explicitly provided (not calculated)\n     * @return data bytes read from contract bytecode\n     * @dev This function performs no input validation on the provided contract,\n     *      other than that there is content to read (but not that its a \"storage contract\")\n     */\n    function readBytesFromBytecode(\n        address _address,\n        uint256 _offset\n    ) public view returns (bytes memory data) {\n        // get the size of the bytecode\n        uint256 bytecodeSize = _bytecodeSizeAt(_address);\n        // handle case where address contains code < _offset\n        if (bytecodeSize < _offset) {\n            revert(\"ContractAsStorage: Read Error\");\n        }\n\n        // handle case where address contains code >= dataOffset\n        // decrement by dataOffset to account for header info\n        uint256 size;\n        unchecked {\n            size = bytecodeSize - _offset;\n        }\n\n        assembly {\n            // allocate free memory\n            data := mload(0x40)\n            // update free memory pointer\n            // use and(x, not(0x1f) as cheaper equivalent to sub(x, mod(x, 0x20)).\n            // adding 0x1f to size + logic above ensures the free memory pointer\n            // remains word-aligned, following the Solidity convention.\n            mstore(0x40, add(data, and(add(add(size, 0x20), 0x1f), not(0x1f))))\n            // store length of data in first 32 bytes\n            mstore(data, size)\n            // copy code to memory, excluding the deployer-address\n            extcodecopy(_address, add(data, 0x20), _offset, size)\n        }\n    }\n\n    /**\n     * @notice Get address for deployer for given contract bytecode\n     * @param _address address of deployed contract with bytecode stored in the V0 or V1 format\n     * @return writerAddress address read from contract bytecode\n     */\n    function getWriterAddressForBytecode(\n        address _address\n    ) public view returns (address) {\n        // get the size of the data\n        uint256 bytecodeSize = _bytecodeSizeAt(_address);\n        // the dataOffset for the bytecode\n        uint256 addressOffset = _bytecodeAddressOffsetAt(_address);\n        // handle case where address contains code < addressOffset + 32 (address takes a whole slot)\n        if (bytecodeSize < (addressOffset + 32)) {\n            revert(\"ContractAsStorage: Read Error\");\n        }\n\n        assembly {\n            // allocate free memory\n            let writerAddress := mload(0x40)\n            // shift free memory pointer by one slot\n            mstore(0x40, add(mload(0x40), 0x20))\n            // copy the 32-byte address of the data contract writer to memory\n            // note: this relies on the assumption noted at the top-level of\n            //       this file that the storage layout for the deployed\n            //       contracts-as-storage contract looks like::\n            //       | invalid opcode | version-string (unless v0) | deployer-address (padded) | data |\n            extcodecopy(\n                _address,\n                writerAddress,\n                addressOffset,\n                0x20 // full 32-bytes, as address is expected to be zero-padded\n            )\n            return(\n                writerAddress,\n                0x20 // return size is entire slot, as it is zero-padded\n            )\n        }\n    }\n\n    /**\n     * @notice Get version for given contract bytecode\n     * @param _address address of deployed contract with bytecode stored in the V0 or V1 format\n     * @return version version read from contract bytecode\n     */\n    function getLibraryVersionForBytecode(\n        address _address\n    ) public view returns (bytes32) {\n        return _bytecodeVersionAt(_address);\n    }\n\n    /**\n     * @notice Get if data are stored in compressed format for given contract bytecode\n     * @param _address address of deployed contract with bytecode stored in the V0, V1, or V2 format\n     * @return isCompressed boolean indicating if the stored data are compressed\n     */\n    function getIsCompressedForBytecode(\n        address _address\n    ) public view returns (bool) {\n        (, bool isCompressed) = _bytecodeDataOffsetAndIsCompressedAt(_address);\n        return isCompressed;\n    }\n\n    /**\n     * Utility function to get the compressed form of a message string using solady LibZip's\n     * flz compress algorithm.\n     * The compressed message is returned as bytes, which may be used as the input to\n     * the function `BytecodeStorageWriter.writeToBytecodeCompressed`.\n     * @param _data string to be compressed\n     * @return bytes compressed bytes\n     */\n    function getCompressed(\n        string memory _data\n    ) public pure returns (bytes memory) {\n        return LibZip.flzCompress(bytes(_data));\n    }\n\n    /*//////////////////////////////////////////////////////////////\n                          INTERNAL HELPER LOGIC\n    //////////////////////////////////////////////////////////////*/\n\n    /**\n     * @notice Returns the size of the bytecode at address `_address`\n     * @param _address address that may or may not contain bytecode\n     * @return size size of the bytecode code at `_address`\n     */\n    function _bytecodeSizeAt(\n        address _address\n    ) private view returns (uint256 size) {\n        assembly {\n            size := extcodesize(_address)\n        }\n        if (size == 0) {\n            revert(\"ContractAsStorage: Read Error\");\n        }\n    }\n\n    /**\n     * @notice Returns the offset of the data in the bytecode at address `_address`\n     * @param _address address that may or may not contain bytecode\n     * @return dataOffset offset of data in bytecode if a known version, otherwise 0\n     * @return isCompressed bool indicating if the stored data are compressed\n     */\n    function _bytecodeDataOffsetAndIsCompressedAt(\n        address _address\n    ) private view returns (uint256 dataOffset, bool isCompressed) {\n        bytes32 version = _bytecodeVersionAt(_address);\n        if (version == V2_VERSION_STRING) {\n            dataOffset = V2_DATA_OFFSET;\n            isCompressed = _isCompressedAt(_address, version);\n        } else if (version == V1_VERSION_STRING) {\n            dataOffset = V1_DATA_OFFSET;\n            // isCompressed remains false, as V1 contracts do not support compression\n        } else if (version == V0_VERSION_STRING) {\n            dataOffset = V0_DATA_OFFSET;\n            // isCompressed remains false, as V0 contracts do not support compression\n        } else {\n            // unknown version, revert\n            revert(\"ContractAsStorage: Unsupported Version\");\n        }\n    }\n\n    /**\n     * @notice Returns the offset of the address in the bytecode at address `_address`\n     * @param _address address that may or may not contain bytecode\n     * @return addressOffset offset of address in bytecode if a known version, otherwise 0\n     */\n    function _bytecodeAddressOffsetAt(\n        address _address\n    ) private view returns (uint256 addressOffset) {\n        bytes32 version = _bytecodeVersionAt(_address);\n        if (version == V2_VERSION_STRING) {\n            addressOffset = V2_ADDRESS_OFFSET;\n        } else if (version == V1_VERSION_STRING) {\n            addressOffset = V1_ADDRESS_OFFSET;\n        } else if (version == V0_VERSION_STRING) {\n            addressOffset = V0_ADDRESS_OFFSET;\n        } else {\n            // unknown version, revert\n            revert(\"ContractAsStorage: Unsupported Version\");\n        }\n    }\n\n    /**\n     * @notice Get version string for given contract bytecode\n     * @param _address address of deployed contract with bytecode stored in the V0 or V1 format\n     * @return version version string read from contract bytecode\n     */\n    function _bytecodeVersionAt(\n        address _address\n    ) private view returns (bytes32 version) {\n        // get the size of the data\n        uint256 bytecodeSize = _bytecodeSizeAt(_address);\n        // handle case where address contains code < minimum expected version string size,\n        // by returning early with the unknown version string\n        if (bytecodeSize < (VERSION_OFFSET + 32)) {\n            return UNKNOWN_VERSION_STRING;\n        }\n\n        assembly {\n            // allocate free memory\n            let versionString := mload(0x40)\n            // shift free memory pointer by one slot\n            mstore(0x40, add(mload(0x40), 0x20))\n            // copy the 32-byte version string of the bytecode library to memory\n            // note: this relies on the assumption noted at the top-level of\n            //       this file that the storage layout for the deployed\n            //       contracts-as-storage contract looks like:\n            //       | invalid opcode | version-string (unless v0) | deployer-address (padded) | data |\n            extcodecopy(\n                _address,\n                versionString,\n                VERSION_OFFSET,\n                0x20 // 32-byte version string\n            )\n            // note: must check against literal strings, as Yul does not allow for\n            //       dynamic strings in switch statements.\n            switch mload(versionString)\n            case \"BytecodeStorage_V2.0.0_________ \" {\n                version := V2_VERSION_STRING\n            }\n            case \"BytecodeStorage_V1.0.0_________ \" {\n                version := V1_VERSION_STRING\n            }\n            case 0x2060486000396000513314601057fe5b60013614601957fe5b6000357fff0000 {\n                // the v0 variant of this library pre-dates formal versioning w/ version strings,\n                // so we check the first 32 bytes of the execution bytecode itself which\n                // is static and known across all storage contracts deployed with the first version\n                // of this library.\n                version := V0_VERSION_STRING\n            }\n            default {\n                version := UNKNOWN_VERSION_STRING\n            }\n        }\n    }\n\n    /**\n     * @notice Get if stored data are compressed for given contract bytecode\n     * @param _address address of deployed contract with bytecode stored\n     * @param _version version string of the bytecode library used to deploy the contract at `_address`\n     * @return isCompressed bool indicating if the stored data are compressed\n     */\n    function _isCompressedAt(\n        address _address,\n        bytes32 _version\n    ) private view returns (bool isCompressed) {\n        // @dev if branch no coverage - unreachable as used, remains for redundant safety\n        if (_version == V0_VERSION_STRING || _version == V1_VERSION_STRING) {\n            // V0 and V1 and unknown contracts do not support compression\n            return false;\n        }\n        // @dev if branch no coverage - unreachable as used, remains for redundant safety\n        if (_version != V2_VERSION_STRING) {\n            // unsupported version, throw error\n            revert(\"ContractAsStorage: Unsupported Version\");\n        }\n        // get the size of the data\n        uint256 bytecodeSize = _bytecodeSizeAt(_address);\n        // handle case where address contains code < minimum expected version string size,\n        // by returning early with false\n        if (bytecodeSize < (COMPRESSION_OFFSET + 1)) {\n            return false;\n        }\n\n        assembly {\n            // allocate free memory\n            let compressedByte := mload(0x40)\n            // shift free memory pointer by one slot\n            mstore(0x40, add(mload(0x40), 0x20))\n            // zero out word at compressedByte (solidity does not guarantee zeroed memory beyond free memory pointer)\n            mstore(compressedByte, 0x00)\n            // copy the 1-byte compressed flag of the bytecode library to memory\n            // note: this relies on the assumption noted at the top-level of\n            //       this file that the storage layout for the deployed\n            //       contracts-as-storage contract looks like:\n            //       | invalid opcode | version-string (unless v0) | deployer-address (padded) | isCompressed | data |\n            extcodecopy(\n                _address,\n                compressedByte,\n                COMPRESSION_OFFSET,\n                0x1 // 1-byte version string\n            )\n            // check if the compressed flag is set\n            switch mload(compressedByte)\n            case 0x00 {\n                isCompressed := false\n            }\n            default {\n                isCompressed := true\n            }\n        }\n    }\n}\n\n/**\n * @title Art Blocks Script Storage Library (Internal, Writes)\n * @author Art Blocks Inc.\n * @notice The internal library for writing to storage contracts. This library is intended to be deployed\n *         within library client contracts that use this library to perform _write_ operations on storage.\n */\nlibrary BytecodeStorageWriter {\n    /*//////////////////////////////////////////////////////////////\n                           WRITE LOGIC\n    //////////////////////////////////////////////////////////////*/\n\n    /**\n     * @notice Write a string to contract bytecode\n     * @param _data string to be written to contract. No input validation is performed on this parameter.\n     * @return address_ address of deployed contract with bytecode stored in the V2 format\n     */\n    function writeToBytecode(\n        string memory _data\n    ) internal returns (address address_) {\n        // prefix bytecode with\n        bytes memory creationCode = abi.encodePacked(\n            //---------------------------------------------------------------------------------------------------------------//\n            // Opcode  | Opcode + Arguments  | Description  | Stack View                                                     //\n            //---------------------------------------------------------------------------------------------------------------//\n            // a.) creation code returns all code in the contract except for the first 11 (0B in hex) bytes, as these 11\n            //     bytes are the creation code itself which we do not want to store in the deployed storage contract result\n            //---------------------------------------------------------------------------------------------------------------//\n            // 0x60    |  0x60_0B            | PUSH1 11     | codeOffset                                                     //\n            // 0x59    |  0x59               | MSIZE        | 0 codeOffset                                                   //\n            // 0x81    |  0x81               | DUP2         | codeOffset 0 codeOffset                                        //\n            // 0x38    |  0x38               | CODESIZE     | codeSize codeOffset 0 codeOffset                               //\n            // 0x03    |  0x03               | SUB          | (codeSize - codeOffset) 0 codeOffset                           //\n            // 0x80    |  0x80               | DUP          | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset   //\n            // 0x92    |  0x92               | SWAP3        | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset)   //\n            // 0x59    |  0x59               | MSIZE        | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) //\n            // 0x39    |  0x39               | CODECOPY     | 0 (codeSize - codeOffset)                                      //\n            // 0xF3    |  0xF3               | RETURN       |                                                                //\n            //---------------------------------------------------------------------------------------------------------------//\n            // (11 bytes)\n            hex\"60_0B_59_81_38_03_80_92_59_39_F3\",\n            //---------------------------------------------------------------------------------------------------------------//\n            // b.) ensure that the deployed storage contract is non-executeable (first opcode is the `invalid` opcode)\n            //---------------------------------------------------------------------------------------------------------------//\n            //---------------------------------------------------------------------------------------------------------------//\n            // 0xFE    |  0xFE               | INVALID      |                                                                //\n            //---------------------------------------------------------------------------------------------------------------//\n            // (1 byte)\n            hex\"FE\",\n            //---------------------------------------------------------------------------------------------------------------//\n            // c.) store the version string, which is already represented as a 32-byte value\n            //---------------------------------------------------------------------------------------------------------------//\n            // (32 bytes)\n            BytecodeStorageReader.CURRENT_VERSION,\n            //---------------------------------------------------------------------------------------------------------------//\n            // d.) store the deploying-contract's address with 0-padding to fit a 20-byte address into a 32-byte slot\n            //---------------------------------------------------------------------------------------------------------------//\n            // (12 bytes)\n            hex\"00_00_00_00_00_00_00_00_00_00_00_00\",\n            // (20 bytes)\n            address(this),\n            //---------------------------------------------------------------------------------------------------------------//\n            // e.) store the bool indicating if the data is compressed. true for this function.\n            //---------------------------------------------------------------------------------------------------------------//\n            // (1 byte)\n            hex\"00\",\n            // uploaded data (stored as bytecode) comes last\n            _data\n        );\n\n        assembly {\n            // deploy a new contract with the generated creation code.\n            // start 32 bytes into creationCode to avoid copying the byte length.\n            address_ := create(0, add(creationCode, 0x20), mload(creationCode))\n        }\n\n        // address must be non-zero if contract was deployed successfully\n        require(address_ != address(0), \"ContractAsStorage: Write Error\");\n    }\n\n    /**\n     * @notice Write a string to contract bytecode, input as compressed bytes from solady LibZip\n     * @param _dataCompressed compressed bytes to be written to contract. No input validation is performed on this parameter.\n     * @return address_ address of deployed contract with bytecode stored in the V2 format\n     */\n    function writeToBytecodeCompressed(\n        bytes memory _dataCompressed\n    ) internal returns (address address_) {\n        // prefix bytecode with\n        bytes memory creationCode = abi.encodePacked(\n            //---------------------------------------------------------------------------------------------------------------//\n            // Opcode  | Opcode + Arguments  | Description  | Stack View                                                     //\n            //---------------------------------------------------------------------------------------------------------------//\n            // a.) creation code returns all code in the contract except for the first 11 (0B in hex) bytes, as these 11\n            //     bytes are the creation code itself which we do not want to store in the deployed storage contract result\n            //---------------------------------------------------------------------------------------------------------------//\n            // 0x60    |  0x60_0B            | PUSH1 11     | codeOffset                                                     //\n            // 0x59    |  0x59               | MSIZE        | 0 codeOffset                                                   //\n            // 0x81    |  0x81               | DUP2         | codeOffset 0 codeOffset                                        //\n            // 0x38    |  0x38               | CODESIZE     | codeSize codeOffset 0 codeOffset                               //\n            // 0x03    |  0x03               | SUB          | (codeSize - codeOffset) 0 codeOffset                           //\n            // 0x80    |  0x80               | DUP          | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset   //\n            // 0x92    |  0x92               | SWAP3        | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset)   //\n            // 0x59    |  0x59               | MSIZE        | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) //\n            // 0x39    |  0x39               | CODECOPY     | 0 (codeSize - codeOffset)                                      //\n            // 0xF3    |  0xF3               | RETURN       |                                                                //\n            //---------------------------------------------------------------------------------------------------------------//\n            // (11 bytes)\n            hex\"60_0B_59_81_38_03_80_92_59_39_F3\",\n            //---------------------------------------------------------------------------------------------------------------//\n            // b.) ensure that the deployed storage contract is non-executeable (first opcode is the `invalid` opcode)\n            //---------------------------------------------------------------------------------------------------------------//\n            //---------------------------------------------------------------------------------------------------------------//\n            // 0xFE    |  0xFE               | INVALID      |                                                                //\n            //---------------------------------------------------------------------------------------------------------------//\n            // (1 byte)\n            hex\"FE\",\n            //---------------------------------------------------------------------------------------------------------------//\n            // c.) store the version string, which is already represented as a 32-byte value\n            //---------------------------------------------------------------------------------------------------------------//\n            // (32 bytes)\n            BytecodeStorageReader.CURRENT_VERSION,\n            //---------------------------------------------------------------------------------------------------------------//\n            // d.) store the deploying-contract's address with 0-padding to fit a 20-byte address into a 32-byte slot\n            //---------------------------------------------------------------------------------------------------------------//\n            // (12 bytes)\n            hex\"00_00_00_00_00_00_00_00_00_00_00_00\",\n            // (20 bytes)\n            address(this),\n            //---------------------------------------------------------------------------------------------------------------//\n            // e.) store the bool indicating if the data is compressed. true for this function.\n            //---------------------------------------------------------------------------------------------------------------//\n            // (1 byte)\n            hex\"01\",\n            // uploaded compressed data (stored as bytecode) comes last\n            _dataCompressed\n        );\n\n        assembly {\n            // deploy a new contract with the generated creation code.\n            // start 32 bytes into creationCode to avoid copying the byte length.\n            address_ := create(0, add(creationCode, 0x20), mload(creationCode))\n        }\n\n        // address must be non-zero if contract was deployed successfully\n        require(address_ != address(0), \"ContractAsStorage: Write Error\");\n    }\n}\n"
      },
      "contracts/libs/v0.8.x/Bytes32Strings.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Created By: Art Blocks Inc.\n// Inspired by: https://ethereum.stackexchange.com/a/123950/103422\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Operations on bytes32 data type, dealing with conversion to string.\n */\nlibrary Bytes32Strings {\n    /**\n     * @notice Intended to convert a `bytes32`-encoded string literal to `string`.\n     * Trims zero padding to arrive at original string literal.\n     */\n    function toString(\n        bytes32 source\n    ) internal pure returns (string memory result) {\n        uint8 length;\n        while (source[length] != 0 && length < 32) {\n            length++;\n        }\n        assembly {\n            // free memory pointer\n            result := mload(0x40)\n            // update free memory pointer to new \"memory end\"\n            // (offset is 64-bytes: 32 for length, 32 for data)\n            mstore(0x40, add(result, 0x40))\n            // store length in first 32-byte memory slot\n            mstore(result, length)\n            // write actual data in second 32-byte memory slot\n            mstore(add(result, 0x20), source)\n        }\n    }\n\n    /**\n     * @notice Intended to check if a `bytes32`-encoded string contains a given\n     * character with UTF-8 character code `utf8CharCode exactly `targetQty`\n     * times. Does not support searching for multi-byte characters, only\n     * characters with UTF-8 character codes < 0x80.\n     */\n    function containsExactCharacterQty(\n        bytes32 source,\n        uint8 utf8CharCode,\n        uint8 targetQty\n    ) internal pure returns (bool) {\n        uint8 _occurrences;\n        uint8 i;\n        for (i; i < 32; ) {\n            uint8 _charCode = uint8(source[i]);\n            // if not a null byte, or a multi-byte UTF-8 character, check match\n            if (_charCode != 0 && _charCode < 0x80) {\n                if (_charCode == utf8CharCode) {\n                    unchecked {\n                        // no risk of overflow since max 32 iterations < max uin8=255\n                        ++_occurrences;\n                    }\n                }\n            }\n            unchecked {\n                // no risk of overflow since max 32 iterations < max uin8=255\n                ++i;\n            }\n        }\n        return _occurrences == targetQty;\n    }\n}\n"
      },
      "contracts/libs/v0.8.x/ERC721_PackedHashSeedV1.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/ERC721.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC721} from \"@openzeppelin-5.0/contracts/token/ERC721/IERC721.sol\";\nimport {IERC721Receiver} from \"@openzeppelin-5.0/contracts/token/ERC721/IERC721Receiver.sol\";\nimport {IERC721Metadata} from \"@openzeppelin-5.0/contracts/token/ERC721/extensions/IERC721Metadata.sol\";\nimport {Context} from \"@openzeppelin-5.0/contracts/utils/Context.sol\";\nimport {Strings} from \"@openzeppelin-5.0/contracts/utils/Strings.sol\";\nimport {IERC165, ERC165} from \"@openzeppelin-5.0/contracts/utils/introspection/ERC165.sol\";\nimport {IERC721Errors} from \"@openzeppelin-5.0/contracts/interfaces/draft-IERC6093.sol\";\n\n/**\n * @dev Forked version of the OpenZeppelin v5.0.1 ERC721 contract. Updated\n * with an initialize function to ensure EIP 1167 compatibility. Utilizes\n * a struct to pack owner and hash seed into a single storage slot.\n * ---------------------\n * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including\n * the Metadata extension, but not including the Enumerable extension, which is available separately as\n * {ERC721Enumerable}.\n */\nabstract contract ERC721_PackedHashSeedV1 is\n    Context,\n    ERC165,\n    IERC721,\n    IERC721Metadata,\n    IERC721Errors\n{\n    using Strings for uint256;\n\n    // Token name\n    string private _name;\n\n    // Token symbol\n    string private _symbol;\n\n    /// ensure initialization can only be performed once\n    bool private _initialized;\n\n    /// struct to pack a token owner and hash seed into same storage slot\n    struct OwnerAndHashSeed {\n        // 20 bytes for address of token's owner\n        address owner;\n        // remaining 12 bytes allocated to token hash seed\n        bytes12 hashSeed;\n    }\n\n    /// mapping of token ID to OwnerAndHashSeed\n    /// @dev visibility internal so inheriting contracts can access\n    mapping(uint256 tokenId => OwnerAndHashSeed) internal _ownersAndHashSeeds;\n\n    mapping(address owner => uint256) private _balances;\n\n    mapping(uint256 tokenId => address) private _tokenApprovals;\n\n    mapping(address owner => mapping(address operator => bool))\n        private _operatorApprovals;\n\n    // @dev constructor intentionally removed to allow for EIP 1167 compatibility,\n    // see `initialize` function for contract initialization\n\n    /**\n     * @dev See {IERC165-supportsInterface}.\n     */\n    function supportsInterface(\n        bytes4 interfaceId\n    ) public view virtual override(ERC165, IERC165) returns (bool) {\n        return\n            interfaceId == type(IERC721).interfaceId ||\n            interfaceId == type(IERC721Metadata).interfaceId ||\n            super.supportsInterface(interfaceId);\n    }\n\n    /**\n     * @dev See {IERC721-balanceOf}.\n     */\n    function balanceOf(address owner) public view virtual returns (uint256) {\n        if (owner == address(0)) {\n            revert ERC721InvalidOwner(address(0));\n        }\n        return _balances[owner];\n    }\n\n    /**\n     * @dev See {IERC721-ownerOf}.\n     */\n    function ownerOf(uint256 tokenId) public view virtual returns (address) {\n        return _requireOwned(tokenId);\n    }\n\n    /**\n     * @dev See {IERC721Metadata-name}.\n     */\n    function name() public view virtual returns (string memory) {\n        return _name;\n    }\n\n    /**\n     * @dev See {IERC721Metadata-symbol}.\n     */\n    function symbol() public view virtual returns (string memory) {\n        return _symbol;\n    }\n\n    /**\n     * @dev See {IERC721Metadata-tokenURI}.\n     */\n    function tokenURI(\n        uint256 tokenId\n    ) public view virtual returns (string memory) {\n        _requireOwned(tokenId);\n\n        string memory baseURI = _baseURI();\n        return\n            bytes(baseURI).length > 0\n                ? string.concat(baseURI, tokenId.toString())\n                : \"\";\n    }\n\n    /**\n     * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each\n     * token will be the concatenation of the `baseURI` and the `tokenId`. Empty\n     * by default, can be overridden in child contracts.\n     */\n    function _baseURI() internal view virtual returns (string memory) {\n        return \"\";\n    }\n\n    /**\n     * @dev See {IERC721-approve}.\n     */\n    function approve(address to, uint256 tokenId) public virtual {\n        _approve(to, tokenId, _msgSender());\n    }\n\n    /**\n     * @dev See {IERC721-getApproved}.\n     */\n    function getApproved(\n        uint256 tokenId\n    ) public view virtual returns (address) {\n        _requireOwned(tokenId);\n\n        return _getApproved(tokenId);\n    }\n\n    /**\n     * @dev See {IERC721-setApprovalForAll}.\n     */\n    function setApprovalForAll(address operator, bool approved) public virtual {\n        _setApprovalForAll(_msgSender(), operator, approved);\n    }\n\n    /**\n     * @dev See {IERC721-isApprovedForAll}.\n     */\n    function isApprovedForAll(\n        address owner,\n        address operator\n    ) public view virtual returns (bool) {\n        return _operatorApprovals[owner][operator];\n    }\n\n    /**\n     * @dev See {IERC721-transferFrom}.\n     */\n    function transferFrom(\n        address from,\n        address to,\n        uint256 tokenId\n    ) public virtual {\n        if (to == address(0)) {\n            revert ERC721InvalidReceiver(address(0));\n        }\n        // Setting an \"auth\" arguments enables the `_isAuthorized` check which verifies that the token exists\n        // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here.\n        address previousOwner = _update(to, tokenId, _msgSender());\n        if (previousOwner != from) {\n            revert ERC721IncorrectOwner(from, tokenId, previousOwner);\n        }\n    }\n\n    /**\n     * @dev See {IERC721-safeTransferFrom}.\n     */\n    function safeTransferFrom(\n        address from,\n        address to,\n        uint256 tokenId\n    ) public {\n        safeTransferFrom(from, to, tokenId, \"\");\n    }\n\n    /**\n     * @dev See {IERC721-safeTransferFrom}.\n     */\n    function safeTransferFrom(\n        address from,\n        address to,\n        uint256 tokenId,\n        bytes memory data\n    ) public virtual {\n        transferFrom(from, to, tokenId);\n        _checkOnERC721Received(from, to, tokenId, data);\n    }\n\n    /**\n     * @notice Initializes the contract by setting a `name` and a `symbol` to the token collection.\n     * This function should be called atomically, immediately after deployment.\n     * Only callable once.\n     * @param name_ Name for the token collection.\n     * @param symbol_ Symbol for the token collection.\n     */\n    function initialize(string memory name_, string memory symbol_) internal {\n        require(!_initialized, \"Already initialized\");\n        _name = name_;\n        _symbol = symbol_;\n        _initialized = true;\n    }\n\n    /**\n     * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist\n     *\n     * IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the\n     * core ERC721 logic MUST be matched with the use of {_increaseBalance} to keep balances\n     * consistent with ownership. The invariant to preserve is that for any address `a` the value returned by\n     * `balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`.\n     */\n    function _ownerOf(uint256 tokenId) internal view virtual returns (address) {\n        return _ownersAndHashSeeds[tokenId].owner;\n    }\n\n    /**\n     * @dev Returns the approved address for `tokenId`. Returns 0 if `tokenId` is not minted.\n     */\n    function _getApproved(\n        uint256 tokenId\n    ) internal view virtual returns (address) {\n        return _tokenApprovals[tokenId];\n    }\n\n    /**\n     * @dev Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in\n     * particular (ignoring whether it is owned by `owner`).\n     *\n     * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this\n     * assumption.\n     */\n    function _isAuthorized(\n        address owner,\n        address spender,\n        uint256 tokenId\n    ) internal view virtual returns (bool) {\n        return\n            spender != address(0) &&\n            (owner == spender ||\n                isApprovedForAll(owner, spender) ||\n                _getApproved(tokenId) == spender);\n    }\n\n    /**\n     * @dev Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner.\n     * Reverts if `spender` does not have approval from the provided `owner` for the given token or for all its assets\n     * the `spender` for the specific `tokenId`.\n     *\n     * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this\n     * assumption.\n     */\n    function _checkAuthorized(\n        address owner,\n        address spender,\n        uint256 tokenId\n    ) internal view virtual {\n        if (!_isAuthorized(owner, spender, tokenId)) {\n            if (owner == address(0)) {\n                revert ERC721NonexistentToken(tokenId);\n            } else {\n                revert ERC721InsufficientApproval(spender, tokenId);\n            }\n        }\n    }\n\n    /**\n     * @dev Unsafe write access to the balances, used by extensions that \"mint\" tokens using an {ownerOf} override.\n     *\n     * NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that\n     * a uint256 would ever overflow from increments when these increments are bounded to uint128 values.\n     *\n     * WARNING: Increasing an account's balance using this function tends to be paired with an override of the\n     * {_ownerOf} function to resolve the ownership of the corresponding tokens so that balances and ownership\n     * remain consistent with one another.\n     */\n    function _increaseBalance(address account, uint128 value) internal virtual {\n        unchecked {\n            _balances[account] += value;\n        }\n    }\n\n    /**\n     * @dev Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner\n     * (or `to`) is the zero address. Returns the owner of the `tokenId` before the update.\n     *\n     * The `auth` argument is optional. If the value passed is non 0, then this function will check that\n     * `auth` is either the owner of the token, or approved to operate on the token (by the owner).\n     *\n     * Emits a {Transfer} event.\n     *\n     * NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}.\n     */\n    function _update(\n        address to,\n        uint256 tokenId,\n        address auth\n    ) internal virtual returns (address) {\n        address from = _ownerOf(tokenId);\n\n        // Perform (optional) operator check\n        if (auth != address(0)) {\n            _checkAuthorized(from, auth, tokenId);\n        }\n\n        // Execute the update\n        if (from != address(0)) {\n            // Clear approval. No need to re-authorize or emit the Approval event\n            _approve(address(0), tokenId, address(0), false);\n\n            unchecked {\n                _balances[from] -= 1;\n            }\n        }\n\n        if (to != address(0)) {\n            unchecked {\n                _balances[to] += 1;\n            }\n        }\n\n        _ownersAndHashSeeds[tokenId].owner = to;\n\n        emit Transfer(from, to, tokenId);\n\n        return from;\n    }\n\n    /**\n     * @dev Mints `tokenId` and transfers it to `to`.\n     *\n     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible\n     *\n     * Requirements:\n     *\n     * - `tokenId` must not exist.\n     * - `to` cannot be the zero address.\n     *\n     * Emits a {Transfer} event.\n     */\n    function _mint(address to, uint256 tokenId) internal {\n        if (to == address(0)) {\n            revert ERC721InvalidReceiver(address(0));\n        }\n        address previousOwner = _update(to, tokenId, address(0));\n        if (previousOwner != address(0)) {\n            revert ERC721InvalidSender(address(0));\n        }\n    }\n\n    /**\n     * @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance.\n     *\n     * Requirements:\n     *\n     * - `tokenId` must not exist.\n     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n     *\n     * Emits a {Transfer} event.\n     */\n    function _safeMint(address to, uint256 tokenId) internal {\n        _safeMint(to, tokenId, \"\");\n    }\n\n    /**\n     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is\n     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.\n     */\n    function _safeMint(\n        address to,\n        uint256 tokenId,\n        bytes memory data\n    ) internal virtual {\n        _mint(to, tokenId);\n        _checkOnERC721Received(address(0), to, tokenId, data);\n    }\n\n    /**\n     * @dev Destroys `tokenId`.\n     * The approval is cleared when the token is burned.\n     * This is an internal function that does not check if the sender is authorized to operate on the token.\n     *\n     * Requirements:\n     *\n     * - `tokenId` must exist.\n     *\n     * Emits a {Transfer} event.\n     */\n    function _burn(uint256 tokenId) internal {\n        address previousOwner = _update(address(0), tokenId, address(0));\n        if (previousOwner == address(0)) {\n            revert ERC721NonexistentToken(tokenId);\n        }\n    }\n\n    /**\n     * @dev Transfers `tokenId` from `from` to `to`.\n     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.\n     *\n     * Requirements:\n     *\n     * - `to` cannot be the zero address.\n     * - `tokenId` token must be owned by `from`.\n     *\n     * Emits a {Transfer} event.\n     */\n    function _transfer(address from, address to, uint256 tokenId) internal {\n        if (to == address(0)) {\n            revert ERC721InvalidReceiver(address(0));\n        }\n        address previousOwner = _update(to, tokenId, address(0));\n        if (previousOwner == address(0)) {\n            revert ERC721NonexistentToken(tokenId);\n        } else if (previousOwner != from) {\n            revert ERC721IncorrectOwner(from, tokenId, previousOwner);\n        }\n    }\n\n    /**\n     * @dev Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients\n     * are aware of the ERC721 standard to prevent tokens from being forever locked.\n     *\n     * `data` is additional data, it has no specified format and it is sent in call to `to`.\n     *\n     * This internal function is like {safeTransferFrom} in the sense that it invokes\n     * {IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g.\n     * implement alternative mechanisms to perform token transfer, such as signature-based.\n     *\n     * Requirements:\n     *\n     * - `tokenId` token must exist and be owned by `from`.\n     * - `to` cannot be the zero address.\n     * - `from` cannot be the zero address.\n     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n     *\n     * Emits a {Transfer} event.\n     */\n    function _safeTransfer(address from, address to, uint256 tokenId) internal {\n        _safeTransfer(from, to, tokenId, \"\");\n    }\n\n    /**\n     * @dev Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is\n     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.\n     */\n    function _safeTransfer(\n        address from,\n        address to,\n        uint256 tokenId,\n        bytes memory data\n    ) internal virtual {\n        _transfer(from, to, tokenId);\n        _checkOnERC721Received(from, to, tokenId, data);\n    }\n\n    /**\n     * @dev Approve `to` to operate on `tokenId`\n     *\n     * The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is\n     * either the owner of the token, or approved to operate on all tokens held by this owner.\n     *\n     * Emits an {Approval} event.\n     *\n     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\n     */\n    function _approve(address to, uint256 tokenId, address auth) internal {\n        _approve(to, tokenId, auth, true);\n    }\n\n    /**\n     * @dev Variant of `_approve` with an optional flag to enable or disable the {Approval} event. The event is not\n     * emitted in the context of transfers.\n     */\n    function _approve(\n        address to,\n        uint256 tokenId,\n        address auth,\n        bool emitEvent\n    ) internal virtual {\n        // Avoid reading the owner unless necessary\n        if (emitEvent || auth != address(0)) {\n            address owner = _requireOwned(tokenId);\n\n            // We do not use _isAuthorized because single-token approvals should not be able to call approve\n            if (\n                auth != address(0) &&\n                owner != auth &&\n                !isApprovedForAll(owner, auth)\n            ) {\n                revert ERC721InvalidApprover(auth);\n            }\n\n            if (emitEvent) {\n                emit Approval(owner, to, tokenId);\n            }\n        }\n\n        _tokenApprovals[tokenId] = to;\n    }\n\n    /**\n     * @dev Approve `operator` to operate on all of `owner` tokens\n     *\n     * Requirements:\n     * - operator can't be the address zero.\n     *\n     * Emits an {ApprovalForAll} event.\n     */\n    function _setApprovalForAll(\n        address owner,\n        address operator,\n        bool approved\n    ) internal virtual {\n        if (operator == address(0)) {\n            revert ERC721InvalidOperator(operator);\n        }\n        _operatorApprovals[owner][operator] = approved;\n        emit ApprovalForAll(owner, operator, approved);\n    }\n\n    /**\n     * @dev Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned).\n     * Returns the owner.\n     *\n     * Overrides to ownership logic should be done to {_ownerOf}.\n     */\n    function _requireOwned(uint256 tokenId) internal view returns (address) {\n        address owner = _ownerOf(tokenId);\n        if (owner == address(0)) {\n            revert ERC721NonexistentToken(tokenId);\n        }\n        return owner;\n    }\n\n    /**\n     * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target address. This will revert if the\n     * recipient doesn't accept the token transfer. The call is not executed if the target address is not a contract.\n     *\n     * @param from address representing the previous owner of the given token ID\n     * @param to target address that will receive the tokens\n     * @param tokenId uint256 ID of the token to be transferred\n     * @param data bytes optional data to send along with the call\n     */\n    function _checkOnERC721Received(\n        address from,\n        address to,\n        uint256 tokenId,\n        bytes memory data\n    ) private {\n        if (to.code.length > 0) {\n            try\n                IERC721Receiver(to).onERC721Received(\n                    _msgSender(),\n                    from,\n                    tokenId,\n                    data\n                )\n            returns (bytes4 retval) {\n                if (retval != IERC721Receiver.onERC721Received.selector) {\n                    revert ERC721InvalidReceiver(to);\n                }\n            } catch (bytes memory reason) {\n                if (reason.length == 0) {\n                    revert ERC721InvalidReceiver(to);\n                } else {\n                    /// @solidity memory-safe-assembly\n                    assembly {\n                        revert(add(32, reason), mload(reason))\n                    }\n                }\n            }\n        }\n    }\n}\n"
      },
      "contracts/libs/v0.8.x/V3FlexLib.sol": {
        "content": "// SPDX-License-Identifier: LGPL-3.0-only\n// Created By: Art Blocks Inc.\n\npragma solidity ^0.8.0;\n\nimport {IGenArt721CoreContractV3_Engine_Flex} from \"../../interfaces/v0.8.x/IGenArt721CoreContractV3_Engine_Flex.sol\";\nimport {IBytecodeStorageReader_Base} from \"../../interfaces/v0.8.x/IBytecodeStorageReader_Base.sol\";\n\nimport {BytecodeStorageWriter} from \"./BytecodeStorageV2.sol\";\n\n/**\n * @title Art Blocks V3 Engine Flex - External Helper Library\n * @notice This library is designed to offload bytecode from the V3 Engine\n * Flex contract. It implements logic that may be accessed via DELEGATECALL for\n * operations related to the V3 Engine Flex contract.\n * @author Art Blocks Inc.\n */\n\nlibrary V3FlexLib {\n    using BytecodeStorageWriter for string;\n    using BytecodeStorageWriter for bytes;\n    // For the purposes of this implementation, due to the limited scope and\n    // existing legacy infrastructure, the library emits the events\n    // defined in IGenArt721CoreContractV3_Engine_Flex.sol. The events are\n    // manually duplicated here\n    /**\n     * @notice When an external asset dependency is updated or added, this event is emitted.\n     * @param _projectId The project ID of the project that was updated.\n     * @param _index The index of the external asset dependency that was updated.\n     * @param _cid Field that contains the CID of the dependency if IPFS or ARWEAVE,\n     * empty string of ONCHAIN, or a string representation of the Art Blocks Dependency\n     * Registry's `dependencyNameAndVersion` if ART_BLOCKS_DEPENDENCY_REGISTRY.\n     * @param _dependencyType The type of the external asset dependency.\n     * @param _externalAssetDependencyCount The number of external asset dependencies.\n     */\n    event ExternalAssetDependencyUpdated(\n        uint256 indexed _projectId,\n        uint256 indexed _index,\n        string _cid,\n        IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependencyType _dependencyType,\n        uint24 _externalAssetDependencyCount\n    );\n\n    /**\n     * @notice The project id `_projectId` has had an external asset dependency removed at index `_index`.\n     */\n    event ExternalAssetDependencyRemoved(\n        uint256 indexed _projectId,\n        uint256 indexed _index\n    );\n\n    /**\n     * @notice The preferred gateway for dependency type `_dependencyType` has been updated to `_gatewayAddress`.\n     */\n    event GatewayUpdated(\n        IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependencyType indexed _dependencyType,\n        string _gatewayAddress\n    );\n\n    /**\n     * @notice The project id `_projectId` has had all external asset dependencies locked.\n     * @dev This is a one-way operation. Once locked, the external asset dependencies cannot be updated.\n     */\n    event ProjectExternalAssetDependenciesLocked(uint256 indexed _projectId);\n\n    // position of V3 Flex Lib storage, using a diamond storage pattern\n    // for this library\n    bytes32 constant V3_FLEX_LIB_STORAGE_POSITION =\n        keccak256(\"v3flexlib.storage\");\n\n    // project-level variables\n    /**\n     * Struct used to store a project's currently configured price, and\n     * whether or not the price has been configured.\n     */\n    struct FlexProjectData {\n        bool externalAssetDependenciesLocked;\n        uint24 externalAssetDependencyCount;\n        mapping(uint256 => IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependency) externalAssetDependencies;\n    }\n\n    // Diamond storage pattern is used in this library\n    struct V3FlexLibStorage {\n        string preferredIPFSGateway;\n        string preferredArweaveGateway;\n        mapping(uint256 projectId => FlexProjectData) flexProjectsData;\n    }\n\n    /**\n     * @notice Updates preferredIPFSGateway to `_gateway`.\n     * @param _gateway The new preferred IPFS gateway.\n     */\n    function updateIPFSGateway(string calldata _gateway) external {\n        s().preferredIPFSGateway = _gateway;\n        emit GatewayUpdated({\n            _dependencyType: IGenArt721CoreContractV3_Engine_Flex\n                .ExternalAssetDependencyType\n                .IPFS,\n            _gatewayAddress: _gateway\n        });\n    }\n\n    /**\n     * @notice Updates preferredArweaveGateway to `_gateway`.\n     * @param _gateway The new preferred Arweave gateway.\n     */\n    function updateArweaveGateway(string calldata _gateway) external {\n        s().preferredArweaveGateway = _gateway;\n        emit GatewayUpdated({\n            _dependencyType: IGenArt721CoreContractV3_Engine_Flex\n                .ExternalAssetDependencyType\n                .ARWEAVE,\n            _gatewayAddress: _gateway\n        });\n    }\n\n    /**\n     * @notice Locks external asset dependencies for project `_projectId`.\n     * Reverts if the external asset dependencies are already locked.\n     * @dev This is a one-way operation. Once locked, the external asset dependencies cannot be updated.\n     * @param _projectId Project to be locked.\n     */\n    function lockProjectExternalAssetDependencies(uint256 _projectId) external {\n        FlexProjectData storage flexProjectData = getFlexProjectData(\n            _projectId\n        );\n        _onlyUnlockedProjectExternalAssetDependencies(flexProjectData);\n        flexProjectData.externalAssetDependenciesLocked = true;\n        emit ProjectExternalAssetDependenciesLocked(_projectId);\n    }\n\n    /**\n     * @notice Updates external asset dependency for project `_projectId`.\n     * @dev Making this an external function adds roughly 1% to the gas cost of adding an asset, but\n     * significantly reduces the bytecode of contracts using this library.\n     * @param _projectId Project to be updated.\n     * @param _index Asset index.\n     * @param _cidOrData Field that contains the CID of the dependency if IPFS or ARWEAVE,\n     * empty string of ONCHAIN, or a string representation of the Art Blocks Dependency\n     * Registry's `dependencyNameAndVersion` if ART_BLOCKS_DEPENDENCY_REGISTRY.\n     * @param _dependencyType Asset dependency type.\n     *  0 - IPFS\n     *  1 - ARWEAVE\n     *  2 - ONCHAIN\n     *  3 - ART_BLOCKS_DEPENDENCY_REGISTRY\n     */\n    function updateProjectExternalAssetDependency(\n        uint256 _projectId,\n        uint256 _index,\n        string memory _cidOrData,\n        IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependencyType _dependencyType\n    ) external {\n        FlexProjectData storage flexProjectData = getFlexProjectData(\n            _projectId\n        );\n        _onlyUnlockedProjectExternalAssetDependencies(flexProjectData);\n        uint24 assetCount = flexProjectData.externalAssetDependencyCount;\n        require(_index < assetCount, \"Asset index out of range\");\n        // @dev dependencyNameAndVersion are not validated against the dependency registry\n        // due to limitations of L1 reads on L2 networks at this time\n\n        IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependency\n            storage _oldDependency = flexProjectData.externalAssetDependencies[\n                _index\n            ];\n        IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependencyType _oldDependencyType = _oldDependency\n                .dependencyType;\n        // update the asset's dependency type to new value in storage\n        flexProjectData\n            .externalAssetDependencies[_index]\n            .dependencyType = _dependencyType;\n        // if the incoming dependency type is onchain, we need to write the data to bytecode\n        if (\n            _dependencyType ==\n            IGenArt721CoreContractV3_Engine_Flex\n                .ExternalAssetDependencyType\n                .ONCHAIN\n        ) {\n            if (\n                _oldDependencyType !=\n                IGenArt721CoreContractV3_Engine_Flex\n                    .ExternalAssetDependencyType\n                    .ONCHAIN\n            ) {\n                // we only need to set the cid to an empty string if we are replacing an offchain asset\n                // an onchain asset will already have an empty cid\n                flexProjectData.externalAssetDependencies[_index].cid = \"\";\n            }\n\n            flexProjectData\n                .externalAssetDependencies[_index]\n                .bytecodeAddress = _cidOrData.writeToBytecode();\n            // we don't want to emit data, so we emit the cid as an empty string\n            _cidOrData = \"\";\n        } else {\n            // incoming dependency type is not ONCHAIN, so we set the cid directly with either\n            // the incoming cid or string representation of the dependencyNameAndVersion\n            flexProjectData.externalAssetDependencies[_index].cid = _cidOrData;\n            // clear any previously populated bytecode address\n            flexProjectData\n                .externalAssetDependencies[_index]\n                .bytecodeAddress = address(0);\n        }\n        emit ExternalAssetDependencyUpdated({\n            _projectId: _projectId,\n            _index: _index,\n            _cid: _cidOrData,\n            _dependencyType: _dependencyType,\n            _externalAssetDependencyCount: assetCount\n        });\n    }\n\n    /**\n     * @notice Updates external asset dependency for project `_projectId` of type\n     * ONCHAIN using on-chain compression. This function stores the string\n     * in a compressed format on-chain. For reads, the compressed script is\n     * decompressed on-chain, ensuring the original text is reconstructed without\n     * external dependencies.\n     * @param _projectId Project to be updated.\n     * @param _index Asset index.\n     * @param _compressedString Pre-compressed string asset to be added.\n     */\n    function updateProjectExternalAssetDependencyOnChainCompressed(\n        uint256 _projectId,\n        uint256 _index,\n        bytes memory _compressedString\n    ) external {\n        FlexProjectData storage flexProjectData = getFlexProjectData(\n            _projectId\n        );\n        _onlyUnlockedProjectExternalAssetDependencies(flexProjectData);\n\n        // check that the index is within the range of the asset count\n        uint24 assetCount = flexProjectData.externalAssetDependencyCount;\n        require(_index < assetCount, \"Asset index out of range\");\n\n        // EFFECTS\n        // overwrite the relevant fields of the previous asset, assigning bytecodeAddress directly\n        IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependency\n            storage currentDependency = flexProjectData\n                .externalAssetDependencies[_index];\n        currentDependency.cid = \"\";\n        currentDependency.dependencyType = IGenArt721CoreContractV3_Engine_Flex\n            .ExternalAssetDependencyType\n            .ONCHAIN;\n        currentDependency.bytecodeAddress = _compressedString\n            .writeToBytecodeCompressed();\n\n        // emit the event\n        emit ExternalAssetDependencyUpdated({\n            _projectId: _projectId,\n            _index: _index,\n            _cid: \"\",\n            _dependencyType: IGenArt721CoreContractV3_Engine_Flex\n                .ExternalAssetDependencyType\n                .ONCHAIN,\n            _externalAssetDependencyCount: assetCount\n        });\n    }\n\n    /**\n     * @notice Updates external asset dependency for project `_projectId` at\n     * index `_index`, with data at BytecodeStorage-compatible address\n     * `_assetAddress`.\n     * @param _projectId Project to be updated.\n     * @param _index Asset index.\n     * @param _assetAddress Address of the on-chain asset.\n     */\n    function updateProjectAssetDependencyOnChainAtAddress(\n        uint256 _projectId,\n        uint256 _index,\n        address _assetAddress\n    ) external {\n        // CHECKS\n        FlexProjectData storage flexProjectData = getFlexProjectData(\n            _projectId\n        );\n        _onlyUnlockedProjectExternalAssetDependencies(flexProjectData);\n        uint24 assetCount = flexProjectData.externalAssetDependencyCount;\n        require(_index < assetCount, \"Asset index out of range\");\n\n        // EFFECTS\n        // overwrite the relevant fields of the previous asset\n        IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependency\n            storage currentDependency = flexProjectData\n                .externalAssetDependencies[_index];\n        currentDependency.cid = \"\";\n        currentDependency.dependencyType = IGenArt721CoreContractV3_Engine_Flex\n            .ExternalAssetDependencyType\n            .ONCHAIN;\n        currentDependency.bytecodeAddress = _assetAddress;\n\n        // emit the event\n        emit ExternalAssetDependencyUpdated({\n            _projectId: _projectId,\n            _index: _index,\n            _cid: \"\",\n            _dependencyType: IGenArt721CoreContractV3_Engine_Flex\n                .ExternalAssetDependencyType\n                .ONCHAIN,\n            _externalAssetDependencyCount: assetCount\n        });\n    }\n\n    /**\n     * @notice Removes external asset dependency for project `_projectId` at index `_index`.\n     * Removal is done by swapping the element to be removed with the last element in the array, then deleting this last element.\n     * Assets with indices higher than `_index` can have their indices adjusted as a result of this operation.\n     * @param _projectId Project to be updated.\n     * @param _index Asset index\n     */\n    function removeProjectExternalAssetDependency(\n        uint256 _projectId,\n        uint256 _index\n    ) external {\n        FlexProjectData storage flexProjectData = getFlexProjectData(\n            _projectId\n        );\n        _onlyUnlockedProjectExternalAssetDependencies(flexProjectData);\n        // ensure the index is within the range of the asset count\n        uint24 assetCount = flexProjectData.externalAssetDependencyCount;\n        require(_index < assetCount, \"Asset index out of range\");\n        // @dev solidity underflow will revert on the following statement if assetCount is 0\n        uint24 lastElementIndex = assetCount - 1;\n        // for UX purposes, only allow removal of the last lastElementIndex\n        require(_index == lastElementIndex, \"Only removal of last asset\");\n\n        // @dev simply delete last element; no need to copy last to deleted index due to require statement above\n\n        delete flexProjectData.externalAssetDependencies[lastElementIndex];\n\n        flexProjectData.externalAssetDependencyCount = lastElementIndex;\n\n        emit ExternalAssetDependencyRemoved({\n            _projectId: _projectId,\n            _index: _index\n        });\n    }\n\n    /**\n     * @notice Adds external asset dependency for project `_projectId`.\n     * @dev Making this an external function adds roughly 1% to the gas cost of adding an asset, but\n     * significantly reduces the bytecode of contracts using this library.\n     * @param _projectId Project to be updated.\n     * @param _cidOrData Field that contains the CID of the dependency if IPFS or ARWEAVE,\n     * empty string of ONCHAIN, or a string representation of the Art Blocks Dependency\n     * Registry's `dependencyNameAndVersion` if ART_BLOCKS_DEPENDENCY_REGISTRY.\n     * @param _dependencyType Asset dependency type.\n     *  0 - IPFS\n     *  1 - ARWEAVE\n     *  2 - ONCHAIN\n     *  3 - ART_BLOCKS_DEPENDENCY_REGISTRY\n     */\n    function addProjectExternalAssetDependency(\n        uint256 _projectId,\n        string memory _cidOrData,\n        IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependencyType _dependencyType\n    ) external {\n        FlexProjectData storage flexProjectData = getFlexProjectData(\n            _projectId\n        );\n        _onlyUnlockedProjectExternalAssetDependencies(flexProjectData);\n        // @dev dependencyNameAndVersion are not validated against the dependency registry\n        // due to limitations of L1 reads on L2 networks at this time\n\n        uint24 assetCount = flexProjectData.externalAssetDependencyCount;\n        address _bytecodeAddress = address(0);\n        // if the incoming dependency type is onchain, we need to write the data to bytecode\n        if (\n            _dependencyType ==\n            IGenArt721CoreContractV3_Engine_Flex\n                .ExternalAssetDependencyType\n                .ONCHAIN\n        ) {\n            _bytecodeAddress = _cidOrData.writeToBytecode();\n            // we don't want to assign or emit data, so we emit the cid as an empty string\n            _cidOrData = \"\";\n        }\n\n        // append the new asset to the end of the project's asset storage array\n        flexProjectData.externalAssetDependencies[\n            assetCount\n        ] = IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependency({\n            cid: _cidOrData,\n            dependencyType: _dependencyType,\n            bytecodeAddress: _bytecodeAddress\n        });\n        // increment the project's asset count\n        flexProjectData.externalAssetDependencyCount = assetCount + 1;\n\n        // emit event indicating the asset has been added\n        emit ExternalAssetDependencyUpdated({\n            _projectId: _projectId,\n            _index: assetCount,\n            _cid: _cidOrData,\n            _dependencyType: _dependencyType,\n            _externalAssetDependencyCount: assetCount + 1\n        });\n    }\n\n    /**\n     * @notice Adds external asset dependency for project `_projectId` of type\n     * ONCHAIN using on-chain compression. This function stores the string\n     * in a compressed format on-chain. For reads, the compressed script is\n     * decompressed on-chain, ensuring the original text is reconstructed without\n     * external dependencies.\n     * @param _projectId Project to be updated.\n     * @param _compressedString Pre-compressed string asset to be added.\n     */\n    function addProjectExternalAssetDependencyOnChainCompressed(\n        uint256 _projectId,\n        bytes memory _compressedString\n    ) external {\n        FlexProjectData storage flexProjectData = getFlexProjectData(\n            _projectId\n        );\n        _onlyUnlockedProjectExternalAssetDependencies(flexProjectData);\n\n        // append the new asset to the end of the project's asset storage array\n        uint24 assetCount = flexProjectData.externalAssetDependencyCount;\n        flexProjectData.externalAssetDependencies[\n            assetCount\n        ] = IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependency({\n            cid: \"\",\n            dependencyType: IGenArt721CoreContractV3_Engine_Flex\n                .ExternalAssetDependencyType\n                .ONCHAIN,\n            bytecodeAddress: _compressedString.writeToBytecodeCompressed()\n        });\n\n        // increment the asset count\n        flexProjectData.externalAssetDependencyCount = assetCount + 1;\n\n        // emit event indicating the asset has been added\n        emit ExternalAssetDependencyUpdated({\n            _projectId: _projectId,\n            _index: assetCount,\n            _cid: \"\",\n            _dependencyType: IGenArt721CoreContractV3_Engine_Flex\n                .ExternalAssetDependencyType\n                .ONCHAIN,\n            _externalAssetDependencyCount: assetCount + 1\n        });\n    }\n\n    /**\n     * @notice Adds an on-chain external asset dependency for project\n     * `_projectId`, with data at BytecodeStorage-compatible address\n     * `_assetAddress`.\n     * @param _projectId Project to be updated.\n     * @param _assetAddress  Address of the BytecodeStorageReader-compatible on-chain asset.\n     */\n    function addProjectAssetDependencyOnChainAtAddress(\n        uint256 _projectId,\n        address _assetAddress\n    ) external {\n        FlexProjectData storage flexProjectData = getFlexProjectData(\n            _projectId\n        );\n        _onlyUnlockedProjectExternalAssetDependencies(flexProjectData);\n\n        // append the new asset to the end of the project's asset storage array\n        uint24 assetCount = flexProjectData.externalAssetDependencyCount;\n        flexProjectData.externalAssetDependencies[\n            assetCount\n        ] = IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependency({\n            cid: \"\",\n            dependencyType: IGenArt721CoreContractV3_Engine_Flex\n                .ExternalAssetDependencyType\n                .ONCHAIN,\n            bytecodeAddress: _assetAddress\n        });\n\n        // increment the asset count\n        flexProjectData.externalAssetDependencyCount = assetCount + 1;\n\n        // emit event indicating the asset has been added\n        emit ExternalAssetDependencyUpdated({\n            _projectId: _projectId,\n            _index: assetCount,\n            _cid: \"\",\n            _dependencyType: IGenArt721CoreContractV3_Engine_Flex\n                .ExternalAssetDependencyType\n                .ONCHAIN,\n            _externalAssetDependencyCount: assetCount + 1\n        });\n    }\n\n    /**\n     * @notice Returns external asset dependency count for project `_projectId` at index `_index`.\n     */\n    function projectExternalAssetDependencyCount(\n        uint256 _projectId\n    ) external view returns (uint256) {\n        FlexProjectData storage flexProjectData = getFlexProjectData(\n            _projectId\n        );\n        return uint256(flexProjectData.externalAssetDependencyCount);\n    }\n\n    /**\n     * @notice Returns external asset dependency for project `_projectId` at index `_index`.\n     * If the dependencyType is ONCHAIN, the `data` field will contain the extrated bytecode data and `cid`\n     * will be an empty string. Conversly, for any other dependencyType, the `data` field will be an empty string\n     * and the `bytecodeAddress` will point to the zero address.\n     * @param _projectId Project to get external asset dependency for.\n     * @param _index Index of the external asset dependency.\n     * @param _bytecodeStorageReaderContract The bytecode storage reader contract to use for reading on-chain\n     * dependencies; only used if the dependency is of type ONCHAIN.\n     */\n    function projectExternalAssetDependencyByIndex(\n        uint256 _projectId,\n        uint256 _index,\n        IBytecodeStorageReader_Base _bytecodeStorageReaderContract\n    )\n        external\n        view\n        returns (\n            IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependencyWithData\n                memory\n        )\n    {\n        FlexProjectData storage flexProjectData = getFlexProjectData(\n            _projectId\n        );\n        IGenArt721CoreContractV3_Engine_Flex.ExternalAssetDependency\n            storage _dependency = flexProjectData.externalAssetDependencies[\n                _index\n            ];\n        address _bytecodeAddress = _dependency.bytecodeAddress;\n\n        return\n            IGenArt721CoreContractV3_Engine_Flex\n                .ExternalAssetDependencyWithData({\n                    dependencyType: _dependency.dependencyType,\n                    cid: _dependency.cid,\n                    bytecodeAddress: _bytecodeAddress,\n                    data: (_dependency.dependencyType ==\n                        IGenArt721CoreContractV3_Engine_Flex\n                            .ExternalAssetDependencyType\n                            .ONCHAIN)\n                        ? _bytecodeStorageReaderContract.readFromBytecode(\n                            _bytecodeAddress\n                        )\n                        : \"\"\n                });\n    }\n\n    /**\n     * @notice Returns the preferred IPFS gateway.\n     */\n    function preferredIPFSGateway() external view returns (string memory) {\n        return s().preferredIPFSGateway;\n    }\n\n    /**\n     * @notice Returns the preferred Arweave gateway.\n     */\n    function preferredArweaveGateway() external view returns (string memory) {\n        return s().preferredArweaveGateway;\n    }\n\n    /**\n     * @notice Loads the FlexProjectData for a given project.\n     * @param projectId Project Id to get FlexProjectData for\n     */\n    function getFlexProjectData(\n        uint256 projectId\n    ) internal view returns (FlexProjectData storage) {\n        return s().flexProjectsData[projectId];\n    }\n\n    /**\n     * @notice Return the storage struct for reading and writing. This library\n     * uses a diamond storage pattern when managing storage.\n     * @return storageStruct The V3FlexLibStorage struct.\n     */\n    function s()\n        internal\n        pure\n        returns (V3FlexLibStorage storage storageStruct)\n    {\n        bytes32 position = V3_FLEX_LIB_STORAGE_POSITION;\n        assembly (\"memory-safe\") {\n            storageStruct.slot := position\n        }\n    }\n\n    function _onlyUnlockedProjectExternalAssetDependencies(\n        FlexProjectData storage flexProjectData\n    ) private view {\n        require(\n            !flexProjectData.externalAssetDependenciesLocked,\n            \"External dependencies locked\"\n        );\n    }\n}\n"
      },
      "solady/src/utils/LibZip.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/// @notice Library for compressing and decompressing bytes.\n/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibZip.sol)\n/// @author Calldata compression by clabby (https://github.com/clabby/op-kompressor)\n/// @author FastLZ by ariya (https://github.com/ariya/FastLZ)\n///\n/// @dev Note:\n/// The accompanying solady.js library includes implementations of\n/// FastLZ and calldata operations for convenience.\nlibrary LibZip {\n    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n    /*                     FAST LZ OPERATIONS                     */\n    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n    // LZ77 implementation based on FastLZ.\n    // Equivalent to level 1 compression and decompression at the following commit:\n    // https://github.com/ariya/FastLZ/commit/344eb4025f9ae866ebf7a2ec48850f7113a97a42\n    // Decompression is backwards compatible.\n\n    /// @dev Returns the compressed `data`.\n    function flzCompress(bytes memory data) internal pure returns (bytes memory result) {\n        /// @solidity memory-safe-assembly\n        assembly {\n            function ms8(d_, v_) -> _d {\n                mstore8(d_, v_)\n                _d := add(d_, 1)\n            }\n            function u24(p_) -> _u {\n                _u := mload(p_)\n                _u := or(shl(16, byte(2, _u)), or(shl(8, byte(1, _u)), byte(0, _u)))\n            }\n            function cmp(p_, q_, e_) -> _l {\n                for { e_ := sub(e_, q_) } lt(_l, e_) { _l := add(_l, 1) } {\n                    e_ := mul(iszero(byte(0, xor(mload(add(p_, _l)), mload(add(q_, _l))))), e_)\n                }\n            }\n            function literals(runs_, src_, dest_) -> _o {\n                for { _o := dest_ } iszero(lt(runs_, 0x20)) { runs_ := sub(runs_, 0x20) } {\n                    mstore(ms8(_o, 31), mload(src_))\n                    _o := add(_o, 0x21)\n                    src_ := add(src_, 0x20)\n                }\n                if iszero(runs_) { leave }\n                mstore(ms8(_o, sub(runs_, 1)), mload(src_))\n                _o := add(1, add(_o, runs_))\n            }\n            function mt(l_, d_, o_) -> _o {\n                for { d_ := sub(d_, 1) } iszero(lt(l_, 263)) { l_ := sub(l_, 262) } {\n                    o_ := ms8(ms8(ms8(o_, add(224, shr(8, d_))), 253), and(0xff, d_))\n                }\n                if iszero(lt(l_, 7)) {\n                    _o := ms8(ms8(ms8(o_, add(224, shr(8, d_))), sub(l_, 7)), and(0xff, d_))\n                    leave\n                }\n                _o := ms8(ms8(o_, add(shl(5, l_), shr(8, d_))), and(0xff, d_))\n            }\n            function setHash(i_, v_) {\n                let p_ := add(mload(0x40), shl(2, i_))\n                mstore(p_, xor(mload(p_), shl(224, xor(shr(224, mload(p_)), v_))))\n            }\n            function getHash(i_) -> _h {\n                _h := shr(224, mload(add(mload(0x40), shl(2, i_))))\n            }\n            function hash(v_) -> _r {\n                _r := and(shr(19, mul(2654435769, v_)), 0x1fff)\n            }\n            function setNextHash(ip_, ipStart_) -> _ip {\n                setHash(hash(u24(ip_)), sub(ip_, ipStart_))\n                _ip := add(ip_, 1)\n            }\n            result := mload(0x40)\n            codecopy(result, codesize(), 0x8000) // Zeroize the hashmap.\n            let op := add(result, 0x8000)\n            let a := add(data, 0x20)\n            let ipStart := a\n            let ipLimit := sub(add(ipStart, mload(data)), 13)\n            for { let ip := add(2, a) } lt(ip, ipLimit) {} {\n                let r := 0\n                let d := 0\n                for {} 1 {} {\n                    let s := u24(ip)\n                    let h := hash(s)\n                    r := add(ipStart, getHash(h))\n                    setHash(h, sub(ip, ipStart))\n                    d := sub(ip, r)\n                    if iszero(lt(ip, ipLimit)) { break }\n                    ip := add(ip, 1)\n                    if iszero(gt(d, 0x1fff)) { if eq(s, u24(r)) { break } }\n                }\n                if iszero(lt(ip, ipLimit)) { break }\n                ip := sub(ip, 1)\n                if gt(ip, a) { op := literals(sub(ip, a), a, op) }\n                let l := cmp(add(r, 3), add(ip, 3), add(ipLimit, 9))\n                op := mt(l, d, op)\n                ip := setNextHash(setNextHash(add(ip, l), ipStart), ipStart)\n                a := ip\n            }\n            // Copy the result to compact the memory, overwriting the hashmap.\n            let end := sub(literals(sub(add(ipStart, mload(data)), a), a, op), 0x7fe0)\n            let o := add(result, 0x20)\n            mstore(result, sub(end, o)) // Store the length.\n            for {} iszero(gt(o, end)) { o := add(o, 0x20) } { mstore(o, mload(add(o, 0x7fe0))) }\n            mstore(end, 0) // Zeroize the slot after the string.\n            mstore(0x40, add(end, 0x20)) // Allocate the memory.\n        }\n    }\n\n    /// @dev Returns the decompressed `data`.\n    function flzDecompress(bytes memory data) internal pure returns (bytes memory result) {\n        /// @solidity memory-safe-assembly\n        assembly {\n            result := mload(0x40)\n            let op := add(result, 0x20)\n            let end := add(add(data, 0x20), mload(data))\n            for { data := add(data, 0x20) } lt(data, end) {} {\n                let w := mload(data)\n                let c := byte(0, w)\n                let t := shr(5, c)\n                if iszero(t) {\n                    mstore(op, mload(add(data, 1)))\n                    data := add(data, add(2, c))\n                    op := add(op, add(1, c))\n                    continue\n                }\n                for {\n                    let g := eq(t, 7)\n                    let l := add(2, xor(t, mul(g, xor(t, add(7, byte(1, w)))))) // M\n                    let s := add(add(shl(8, and(0x1f, c)), byte(add(1, g), w)), 1) // R\n                    let r := sub(op, s)\n                    let f := xor(s, mul(gt(s, 0x20), xor(s, 0x20)))\n                    let j := 0\n                } 1 {} {\n                    mstore(add(op, j), mload(add(r, j)))\n                    j := add(j, f)\n                    if lt(j, l) { continue }\n                    data := add(data, add(2, g))\n                    op := add(op, l)\n                    break\n                }\n            }\n            mstore(result, sub(op, add(result, 0x20))) // Store the length.\n            mstore(op, 0) // Zeroize the slot after the string.\n            mstore(0x40, add(op, 0x20)) // Allocate the memory.\n        }\n    }\n\n    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n    /*                    CALLDATA OPERATIONS                     */\n    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n    // Calldata compression and decompression using selective run length encoding:\n    // - Sequences of 0x00 (up to 128 consecutive).\n    // - Sequences of 0xff (up to 32 consecutive).\n    //\n    // A run length encoded block consists of two bytes:\n    // (0) 0x00\n    // (1) A control byte with the following bit layout:\n    //     - [7]     `0: 0x00, 1: 0xff`.\n    //     - [0..6]  `runLength - 1`.\n    //\n    // The first 4 bytes are bitwise negated so that the compressed calldata\n    // can be dispatched into the `fallback` and `receive` functions.\n\n    /// @dev Returns the compressed `data`.\n    function cdCompress(bytes memory data) internal pure returns (bytes memory result) {\n        /// @solidity memory-safe-assembly\n        assembly {\n            function rle(v_, o_, d_) -> _o, _d {\n                mstore(o_, shl(240, or(and(0xff, add(d_, 0xff)), and(0x80, v_))))\n                _o := add(o_, 2)\n            }\n            result := mload(0x40)\n            let o := add(result, 0x20)\n            let z := 0 // Number of consecutive 0x00.\n            let y := 0 // Number of consecutive 0xff.\n            for { let end := add(data, mload(data)) } iszero(eq(data, end)) {} {\n                data := add(data, 1)\n                let c := byte(31, mload(data))\n                if iszero(c) {\n                    if y { o, y := rle(0xff, o, y) }\n                    z := add(z, 1)\n                    if eq(z, 0x80) { o, z := rle(0x00, o, 0x80) }\n                    continue\n                }\n                if eq(c, 0xff) {\n                    if z { o, z := rle(0x00, o, z) }\n                    y := add(y, 1)\n                    if eq(y, 0x20) { o, y := rle(0xff, o, 0x20) }\n                    continue\n                }\n                if y { o, y := rle(0xff, o, y) }\n                if z { o, z := rle(0x00, o, z) }\n                mstore8(o, c)\n                o := add(o, 1)\n            }\n            if y { o, y := rle(0xff, o, y) }\n            if z { o, z := rle(0x00, o, z) }\n            // Bitwise negate the first 4 bytes.\n            mstore(add(result, 4), not(mload(add(result, 4))))\n            mstore(result, sub(o, add(result, 0x20))) // Store the length.\n            mstore(o, 0) // Zeroize the slot after the string.\n            mstore(0x40, add(o, 0x20)) // Allocate the memory.\n        }\n    }\n\n    /// @dev Returns the decompressed `data`.\n    function cdDecompress(bytes memory data) internal pure returns (bytes memory result) {\n        /// @solidity memory-safe-assembly\n        assembly {\n            if mload(data) {\n                result := mload(0x40)\n                let o := add(result, 0x20)\n                let s := add(data, 4)\n                let v := mload(s)\n                let end := add(data, mload(data))\n                mstore(s, not(v)) // Bitwise negate the first 4 bytes.\n                for {} lt(data, end) {} {\n                    data := add(data, 1)\n                    let c := byte(31, mload(data))\n                    if iszero(c) {\n                        data := add(data, 1)\n                        let d := byte(31, mload(data))\n                        // Fill with either 0xff or 0x00.\n                        mstore(o, not(0))\n                        if iszero(gt(d, 0x7f)) { codecopy(o, codesize(), add(d, 1)) }\n                        o := add(o, add(and(d, 0x7f), 1))\n                        continue\n                    }\n                    mstore8(o, c)\n                    o := add(o, 1)\n                }\n                mstore(s, v) // Restore the first 4 bytes.\n                mstore(result, sub(o, add(result, 0x20))) // Store the length.\n                mstore(o, 0) // Zeroize the slot after the string.\n                mstore(0x40, add(o, 0x20)) // Allocate the memory.\n            }\n        }\n    }\n\n    /// @dev To be called in the `fallback` function.\n    /// ```\n    ///     fallback() external payable { LibZip.cdFallback(); }\n    ///     receive() external payable {} // Silence compiler warning to add a `receive` function.\n    /// ```\n    /// For efficiency, this function will directly return the results, terminating the context.\n    /// If called internally, it must be called at the end of the function.\n    function cdFallback() internal {\n        assembly {\n            if iszero(calldatasize()) { return(calldatasize(), calldatasize()) }\n            let o := 0\n            let f := not(3) // For negating the first 4 bytes.\n            for { let i := 0 } lt(i, calldatasize()) {} {\n                let c := byte(0, xor(add(i, f), calldataload(i)))\n                i := add(i, 1)\n                if iszero(c) {\n                    let d := byte(0, xor(add(i, f), calldataload(i)))\n                    i := add(i, 1)\n                    // Fill with either 0xff or 0x00.\n                    mstore(o, not(0))\n                    if iszero(gt(d, 0x7f)) { codecopy(o, codesize(), add(d, 1)) }\n                    o := add(o, add(and(d, 0x7f), 1))\n                    continue\n                }\n                mstore8(o, c)\n                o := add(o, 1)\n            }\n            let success := delegatecall(gas(), address(), 0x00, o, codesize(), 0x00)\n            returndatacopy(0x00, 0x00, returndatasize())\n            if iszero(success) { revert(0x00, returndatasize()) }\n            return(0x00, returndatasize())\n        }\n    }\n}\n"
      }
    },
    "settings": {
      "optimizer": {
        "enabled": true,
        "runs": 10
      },
      "evmVersion": "paris",
      "outputSelection": {
        "*": {
          "*": [
            "evm.bytecode",
            "evm.deployedBytecode",
            "devdoc",
            "userdoc",
            "metadata",
            "abi"
          ]
        }
      },
      "libraries": {
        "contracts/libs/v0.8.x/BytecodeStorageV2.sol": {
          "BytecodeStorageReader": "0x000000000016a5a5ff2fa7799c4bee89ba59b74e"
        },
        "contracts/libs/v0.8.x/V3FlexLib.sol": {
          "V3FlexLib": "0x00000000db6f2ebe627260e411e6c973b7c48a62"
        }
      }
    }
  }}