ETH Price: $1,804.50 (-0.46%)

Transaction Decoder

Block:
18530469 at Nov-08-2023 11:18:35 PM +UTC
Transaction Fee:
0.004894257484373938 ETH $8.83
Gas Used:
133,414 Gas / 36.684736867 Gwei

Emitted Events:

418 GenArt721CoreV3_Engine_Flex.Transfer( from=0x00000000...000000000, to=0xAb2c20Cc...F32403Aa4, tokenId=1000042 )
419 GenArt721CoreV3_Engine_Flex.Mint( _to=0xAb2c20Cc...F32403Aa4, _tokenId=1000042 )
420 GnosisSafeProxy.0x3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d( 0x3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d, 0x0000000000000000000000003ade999397f6be92a637acdcbfb875c63f4f96de, 000000000000000000000000000000000000000000000000011c37937e080000 )

Account State Difference:

  Address   Before After State Difference Code
0x5fdf5E6C...07A7e1389
0x8F67Bfd9...637AcaA8B
1.317502413108010686 Eth
Nonce: 56
1.112608155623636748 Eth
Nonce: 57
0.204894257484373938
0xb24EDdc1...CAa0AC105 3.880049064794848128 Eth3.960049064794848128 Eth0.08
0xbe61F1DE...702f67e54 4.298011656721060907 Eth4.398011656721060907 Eth0.1
32.759742523157686331 Eth32.759755864557686331 Eth0.0000133414
0xFc7C0b52...1A56f2ac1 0.84 Eth0.86 Eth0.02

Execution Trace

ETH 0.2 MinterSetPriceV4.purchaseTo( _to=0xAb2c20Cc9365c6A79B6A26040bB0B94F32403Aa4, _projectId=1 ) => ( tokenId=1000042 )
  • MinterFilterV1.mint( _to=0xAb2c20Cc9365c6A79B6A26040bB0B94F32403Aa4, _projectId=1, sender=0x8F67Bfd95a311fbc4e6e95AdF43B077637AcaA8B ) => ( _tokenId=1000042 )
    • GenArt721CoreV3_Engine_Flex.mint_Ecf( _to=0xAb2c20Cc9365c6A79B6A26040bB0B94F32403Aa4, _projectId=1, _by=0x8F67Bfd95a311fbc4e6e95AdF43B077637AcaA8B ) => ( _tokenId=1000042 )
      • 0xd375529b79f2525c058a935d88e95e230334de33.b628171a( )
        • GenArt721CoreV3_Engine_Flex.setTokenHash_8PT( _tokenId=1000042, _hashSeed=D4759B1029D36B0779A7EEFADA6A2628E47DAE720C7E55608492EF28B7A0D49B )
        • GenArt721CoreV3_Engine_Flex.getPrimaryRevenueSplits( _projectId=1, _price=200000000000000000 ) => ( renderProviderRevenue_=20000000000000000, renderProviderAddress_=0xFc7C0b521ed8397E4625842eb00828c1A56f2ac1, platformProviderRevenue_=80000000000000000, platformProviderAddress_=0xb24EDdc13d67FEa233D3810f7A2426ACAa0AC105, artistRevenue_=0, artistAddress_=0x0000000000000000000000000000000000000000, additionalPayeePrimaryRevenue_=100000000000000000, additionalPayeePrimaryAddress_=0xbe61F1DE5A50E219eD5E661DA82766E702f67e54 )
        • ETH 0.08 GnosisSafeProxy.CALL( )
          • ETH 0.08 GnosisSafe.DELEGATECALL( )
          • ETH 0.02 0xfc7c0b521ed8397e4625842eb00828c1a56f2ac1.CALL( )
          • ETH 0.1 0xbe61f1de5a50e219ed5e661da82766e702f67e54.CALL( )
            File 1 of 5: MinterSetPriceV4
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
            pragma solidity ^0.8.0;
            /**
             * @dev Contract module that helps prevent reentrant calls to a function.
             *
             * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
             * available, which can be applied to functions to make sure there are no nested
             * (reentrant) calls to them.
             *
             * Note that because there is a single `nonReentrant` guard, functions marked as
             * `nonReentrant` may not call one another. This can be worked around by making
             * those functions `private`, and then adding `external` `nonReentrant` entry
             * points to them.
             *
             * TIP: If you would like to learn more about reentrancy and alternative ways
             * to protect against it, check out our blog post
             * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
             */
            abstract contract ReentrancyGuard {
                // Booleans are more expensive than uint256 or any type that takes up a full
                // word because each write operation emits an extra SLOAD to first read the
                // slot's contents, replace the bits taken up by the boolean, and then write
                // back. This is the compiler's defense against contract upgrades and
                // pointer aliasing, and it cannot be disabled.
                // The values being non-zero value makes deployment a bit more expensive,
                // but in exchange the refund on every call to nonReentrant will be lower in
                // amount. Since refunds are capped to a percentage of the total
                // transaction's gas, it is best to keep them low in cases like this one, to
                // increase the likelihood of the full refund coming into effect.
                uint256 private constant _NOT_ENTERED = 1;
                uint256 private constant _ENTERED = 2;
                uint256 private _status;
                constructor() {
                    _status = _NOT_ENTERED;
                }
                /**
                 * @dev Prevents a contract from calling itself, directly or indirectly.
                 * Calling a `nonReentrant` function from another `nonReentrant`
                 * function is not supported. It is possible to prevent this from happening
                 * by making the `nonReentrant` function external, and making it call a
                 * `private` function that does the actual work.
                 */
                modifier nonReentrant() {
                    // On the first call to nonReentrant, _notEntered will be true
                    require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                    // Any calls to nonReentrant after this point will fail
                    _status = _ENTERED;
                    _;
                    // By storing the original value once again, a refund is triggered (see
                    // https://eips.ethereum.org/EIPS/eip-2200)
                    _status = _NOT_ENTERED;
                }
            }
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
            pragma solidity ^0.8.0;
            /**
             * @dev Interface of the ERC20 standard as defined in the EIP.
             */
            interface IERC20 {
                /**
                 * @dev Emitted when `value` tokens are moved from one account (`from`) to
                 * another (`to`).
                 *
                 * Note that `value` may be zero.
                 */
                event Transfer(address indexed from, address indexed to, uint256 value);
                /**
                 * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                 * a call to {approve}. `value` is the new allowance.
                 */
                event Approval(address indexed owner, address indexed spender, uint256 value);
                /**
                 * @dev Returns the amount of tokens in existence.
                 */
                function totalSupply() external view returns (uint256);
                /**
                 * @dev Returns the amount of tokens owned by `account`.
                 */
                function balanceOf(address account) external view returns (uint256);
                /**
                 * @dev Moves `amount` tokens from the caller's account to `to`.
                 *
                 * Returns a boolean value indicating whether the operation succeeded.
                 *
                 * Emits a {Transfer} event.
                 */
                function transfer(address to, uint256 amount) external returns (bool);
                /**
                 * @dev Returns the remaining number of tokens that `spender` will be
                 * allowed to spend on behalf of `owner` through {transferFrom}. This is
                 * zero by default.
                 *
                 * This value changes when {approve} or {transferFrom} are called.
                 */
                function allowance(address owner, address spender) external view returns (uint256);
                /**
                 * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                 *
                 * Returns a boolean value indicating whether the operation succeeded.
                 *
                 * IMPORTANT: Beware that changing an allowance with this method brings the risk
                 * that someone may use both the old and the new allowance by unfortunate
                 * transaction ordering. One possible solution to mitigate this race
                 * condition is to first reduce the spender's allowance to 0 and set the
                 * desired value afterwards:
                 * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                 *
                 * Emits an {Approval} event.
                 */
                function approve(address spender, uint256 amount) external returns (bool);
                /**
                 * @dev Moves `amount` tokens from `from` to `to` using the
                 * allowance mechanism. `amount` is then deducted from the caller's
                 * allowance.
                 *
                 * Returns a boolean value indicating whether the operation succeeded.
                 *
                 * Emits a {Transfer} event.
                 */
                function transferFrom(
                    address from,
                    address to,
                    uint256 amount
                ) external returns (bool);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            interface IAdminACLV0 {
                /**
                 * @notice Token ID `_tokenId` minted to `_to`.
                 * @param previousSuperAdmin The previous superAdmin address.
                 * @param newSuperAdmin The new superAdmin address.
                 * @param genArt721CoreAddressesToUpdate Array of genArt721Core
                 * addresses to update to the new superAdmin, for indexing purposes only.
                 */
                event SuperAdminTransferred(
                    address indexed previousSuperAdmin,
                    address indexed newSuperAdmin,
                    address[] genArt721CoreAddressesToUpdate
                );
                /// Type of the Admin ACL contract, e.g. "AdminACLV0"
                function AdminACLType() external view returns (string memory);
                /// super admin address
                function superAdmin() external view returns (address);
                /**
                 * @notice Calls transferOwnership on other contract from this contract.
                 * This is useful for updating to a new AdminACL contract.
                 * @dev this function should be gated to only superAdmin-like addresses.
                 */
                function transferOwnershipOn(
                    address _contract,
                    address _newAdminACL
                ) external;
                /**
                 * @notice Calls renounceOwnership on other contract from this contract.
                 * @dev this function should be gated to only superAdmin-like addresses.
                 */
                function renounceOwnershipOn(address _contract) external;
                /**
                 * @notice Checks if sender `_sender` is allowed to call function with selector
                 * `_selector` on contract `_contract`.
                 */
                function allowed(
                    address _sender,
                    address _contract,
                    bytes4 _selector
                ) external returns (bool);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            interface IFilteredMinterV0 {
                /**
                 * @notice Price per token in wei updated for project `_projectId` to
                 * `_pricePerTokenInWei`.
                 */
                event PricePerTokenInWeiUpdated(
                    uint256 indexed _projectId,
                    uint256 indexed _pricePerTokenInWei
                );
                /**
                 * @notice Currency updated for project `_projectId` to symbol
                 * `_currencySymbol` and address `_currencyAddress`.
                 */
                event ProjectCurrencyInfoUpdated(
                    uint256 indexed _projectId,
                    address indexed _currencyAddress,
                    string _currencySymbol
                );
                /// togglePurchaseToDisabled updated
                event PurchaseToDisabledUpdated(
                    uint256 indexed _projectId,
                    bool _purchaseToDisabled
                );
                // getter function of public variable
                function minterType() external view returns (string memory);
                function genArt721CoreAddress() external returns (address);
                function minterFilterAddress() external returns (address);
                // Triggers a purchase of a token from the desired project, to the
                // TX-sending address.
                function purchase(
                    uint256 _projectId
                ) external payable returns (uint256 tokenId);
                // Triggers a purchase of a token from the desired project, to the specified
                // receiving address.
                function purchaseTo(
                    address _to,
                    uint256 _projectId
                ) external payable returns (uint256 tokenId);
                // Toggles the ability for `purchaseTo` to be called directly with a
                // specified receiving address that differs from the TX-sending address.
                function togglePurchaseToDisabled(uint256 _projectId) external;
                // Called to make the minter contract aware of the max invocations for a
                // given project.
                function setProjectMaxInvocations(uint256 _projectId) external;
                // Gets if token price is configured, token price in wei, currency symbol,
                // and currency address, assuming this is project's minter.
                // Supersedes any defined core price.
                function getPriceInfo(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        bool isConfigured,
                        uint256 tokenPriceInWei,
                        string memory currencySymbol,
                        address currencyAddress
                    );
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            import "./IFilteredMinterV0.sol";
            pragma solidity ^0.8.0;
            /**
             * @title This interface extends the IFilteredMinterV0 interface in order to
             * add support for generic project minter configuration updates.
             * @dev keys represent strings of finite length encoded in bytes32 to minimize
             * gas.
             * @author Art Blocks Inc.
             */
            interface IFilteredMinterV1 is IFilteredMinterV0 {
                /// ANY
                /**
                 * @notice Generic project minter configuration event. Removes key `_key`
                 * for project `_projectId`.
                 */
                event ConfigKeyRemoved(uint256 indexed _projectId, bytes32 _key);
                /// BOOL
                /**
                 * @notice Generic project minter configuration event. Sets value of key
                 * `_key` to `_value` for project `_projectId`.
                 */
                event ConfigValueSet(uint256 indexed _projectId, bytes32 _key, bool _value);
                /// UINT256
                /**
                 * @notice Generic project minter configuration event. Sets value of key
                 * `_key` to `_value` for project `_projectId`.
                 */
                event ConfigValueSet(
                    uint256 indexed _projectId,
                    bytes32 _key,
                    uint256 _value
                );
                /**
                 * @notice Generic project minter configuration event. Adds value `_value`
                 * to the set of uint256 at key `_key` for project `_projectId`.
                 */
                event ConfigValueAddedToSet(
                    uint256 indexed _projectId,
                    bytes32 _key,
                    uint256 _value
                );
                /**
                 * @notice Generic project minter configuration event. Removes value
                 * `_value` to the set of uint256 at key `_key` for project `_projectId`.
                 */
                event ConfigValueRemovedFromSet(
                    uint256 indexed _projectId,
                    bytes32 _key,
                    uint256 _value
                );
                /// ADDRESS
                /**
                 * @notice Generic project minter configuration event. Sets value of key
                 * `_key` to `_value` for project `_projectId`.
                 */
                event ConfigValueSet(
                    uint256 indexed _projectId,
                    bytes32 _key,
                    address _value
                );
                /**
                 * @notice Generic project minter configuration event. Adds value `_value`
                 * to the set of addresses at key `_key` for project `_projectId`.
                 */
                event ConfigValueAddedToSet(
                    uint256 indexed _projectId,
                    bytes32 _key,
                    address _value
                );
                /**
                 * @notice Generic project minter configuration event. Removes value
                 * `_value` to the set of addresses at key `_key` for project `_projectId`.
                 */
                event ConfigValueRemovedFromSet(
                    uint256 indexed _projectId,
                    bytes32 _key,
                    address _value
                );
                /// BYTES32
                /**
                 * @notice Generic project minter configuration event. Sets value of key
                 * `_key` to `_value` for project `_projectId`.
                 */
                event ConfigValueSet(
                    uint256 indexed _projectId,
                    bytes32 _key,
                    bytes32 _value
                );
                /**
                 * @notice Generic project minter configuration event. Adds value `_value`
                 * to the set of bytes32 at key `_key` for project `_projectId`.
                 */
                event ConfigValueAddedToSet(
                    uint256 indexed _projectId,
                    bytes32 _key,
                    bytes32 _value
                );
                /**
                 * @notice Generic project minter configuration event. Removes value
                 * `_value` to the set of bytes32 at key `_key` for project `_projectId`.
                 */
                event ConfigValueRemovedFromSet(
                    uint256 indexed _projectId,
                    bytes32 _key,
                    bytes32 _value
                );
                /**
                 * @dev Strings not supported. Recommend conversion of (short) strings to
                 * bytes32 to remain gas-efficient.
                 */
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            import "./IFilteredMinterV1.sol";
            pragma solidity ^0.8.0;
            /**
             * @title This interface extends the IFilteredMinterV1 interface in order to
             * add support for manually setting project max invocations.
             * @author Art Blocks Inc.
             */
            interface IFilteredMinterV2 is IFilteredMinterV1 {
                /**
                 * @notice Local max invocations for project `_projectId`, tied to core contract `_coreContractAddress`,
                 * updated to `_maxInvocations`.
                 */
                event ProjectMaxInvocationsLimitUpdated(
                    uint256 indexed _projectId,
                    uint256 _maxInvocations
                );
                // Sets the local max invocations for a given project, checking that the provided max invocations is
                // less than or equal to the global max invocations for the project set on the core contract.
                // This does not impact the max invocations value defined on the core contract.
                function manuallyLimitProjectMaxInvocations(
                    uint256 _projectId,
                    uint256 _maxInvocations
                ) external;
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            import "./IAdminACLV0.sol";
            /// use the Royalty Registry's IManifold interface for token royalties
            import "./IManifold.sol";
            /**
             * @title This interface is intended to house interface items that are common
             * across all GenArt721CoreContractV3 flagship and derivative implementations.
             * This interface extends the IManifold royalty interface in order to
             * add support the Royalty Registry by default.
             * @author Art Blocks Inc.
             */
            interface IGenArt721CoreContractV3_Base is IManifold {
                /**
                 * @notice Token ID `_tokenId` minted to `_to`.
                 */
                event Mint(address indexed _to, uint256 indexed _tokenId);
                /**
                 * @notice currentMinter updated to `_currentMinter`.
                 * @dev Implemented starting with V3 core
                 */
                event MinterUpdated(address indexed _currentMinter);
                /**
                 * @notice Platform updated on bytes32-encoded field `_field`.
                 */
                event PlatformUpdated(bytes32 indexed _field);
                /**
                 * @notice Project ID `_projectId` updated on bytes32-encoded field
                 * `_update`.
                 */
                event ProjectUpdated(uint256 indexed _projectId, bytes32 indexed _update);
                event ProposedArtistAddressesAndSplits(
                    uint256 indexed _projectId,
                    address _artistAddress,
                    address _additionalPayeePrimarySales,
                    uint256 _additionalPayeePrimarySalesPercentage,
                    address _additionalPayeeSecondarySales,
                    uint256 _additionalPayeeSecondarySalesPercentage
                );
                event AcceptedArtistAddressesAndSplits(uint256 indexed _projectId);
                // version and type of the core contract
                // coreVersion is a string of the form "0.x.y"
                function coreVersion() external view returns (string memory);
                // coreType is a string of the form "GenArt721CoreV3"
                function coreType() external view returns (string memory);
                // owner (pre-V3 was named admin) of contract
                // this is expected to be an Admin ACL contract for V3
                function owner() external view returns (address);
                // Admin ACL contract for V3, will be at the address owner()
                function adminACLContract() external returns (IAdminACLV0);
                // backwards-compatible (pre-V3) admin - equal to owner()
                function admin() external view returns (address);
                /**
                 * Function determining if _sender is allowed to call function with
                 * selector _selector on contract `_contract`. Intended to be used with
                 * peripheral contracts such as minters, as well as internally by the
                 * core contract itself.
                 */
                function adminACLAllowed(
                    address _sender,
                    address _contract,
                    bytes4 _selector
                ) external returns (bool);
                /// getter function of public variable
                function startingProjectId() external view returns (uint256);
                // getter function of public variable
                function nextProjectId() external view returns (uint256);
                // getter function of public mapping
                function tokenIdToProjectId(
                    uint256 tokenId
                ) external view returns (uint256 projectId);
                // @dev this is not available in V0
                function isMintWhitelisted(address minter) external view returns (bool);
                function projectIdToArtistAddress(
                    uint256 _projectId
                ) external view returns (address payable);
                function projectIdToAdditionalPayeePrimarySales(
                    uint256 _projectId
                ) external view returns (address payable);
                function projectIdToAdditionalPayeePrimarySalesPercentage(
                    uint256 _projectId
                ) external view returns (uint256);
                function projectIdToSecondaryMarketRoyaltyPercentage(
                    uint256 _projectId
                ) external view returns (uint256);
                function projectURIInfo(
                    uint256 _projectId
                ) external view returns (string memory projectBaseURI);
                // @dev new function in V3
                function projectStateData(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        uint256 invocations,
                        uint256 maxInvocations,
                        bool active,
                        bool paused,
                        uint256 completedTimestamp,
                        bool locked
                    );
                function projectDetails(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        string memory projectName,
                        string memory artist,
                        string memory description,
                        string memory website,
                        string memory license
                    );
                function projectScriptDetails(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        string memory scriptTypeAndVersion,
                        string memory aspectRatio,
                        uint256 scriptCount
                    );
                function projectScriptByIndex(
                    uint256 _projectId,
                    uint256 _index
                ) external view returns (string memory);
                function tokenIdToHash(uint256 _tokenId) external view returns (bytes32);
                // function to set a token's hash (must be guarded)
                function setTokenHash_8PT(uint256 _tokenId, bytes32 _hash) external;
                // @dev gas-optimized signature in V3 for `mint`
                function mint_Ecf(
                    address _to,
                    uint256 _projectId,
                    address _by
                ) external returns (uint256 tokenId);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            import "./IAdminACLV0.sol";
            import "./IGenArt721CoreContractV3_Base.sol";
            interface IGenArt721CoreContractV3_Engine is IGenArt721CoreContractV3_Base {
                // @dev new function in V3
                function getPrimaryRevenueSplits(
                    uint256 _projectId,
                    uint256 _price
                )
                    external
                    view
                    returns (
                        uint256 renderProviderRevenue_,
                        address payable renderProviderAddress_,
                        uint256 platformProviderRevenue_,
                        address payable platformProviderAddress_,
                        uint256 artistRevenue_,
                        address payable artistAddress_,
                        uint256 additionalPayeePrimaryRevenue_,
                        address payable additionalPayeePrimaryAddress_
                    );
                // @dev The render provider primary sales payment address
                function renderProviderPrimarySalesAddress()
                    external
                    view
                    returns (address payable);
                // @dev The platform provider primary sales payment address
                function platformProviderPrimarySalesAddress()
                    external
                    view
                    returns (address payable);
                // @dev Percentage of primary sales allocated to the render provider
                function renderProviderPrimarySalesPercentage()
                    external
                    view
                    returns (uint256);
                // @dev Percentage of primary sales allocated to the platform provider
                function platformProviderPrimarySalesPercentage()
                    external
                    view
                    returns (uint256);
                // @dev The render provider secondary sales royalties payment address
                function renderProviderSecondarySalesAddress()
                    external
                    view
                    returns (address payable);
                // @dev The platform provider secondary sales royalties payment address
                function platformProviderSecondarySalesAddress()
                    external
                    view
                    returns (address payable);
                // @dev Basis points of secondary sales allocated to the render provider
                function renderProviderSecondarySalesBPS() external view returns (uint256);
                // @dev Basis points of secondary sales allocated to the platform provider
                function platformProviderSecondarySalesBPS()
                    external
                    view
                    returns (uint256);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            import "./IAdminACLV0.sol";
            import "./IGenArt721CoreContractV3_Base.sol";
            /**
             * @title This interface extends IGenArt721CoreContractV3_Base with functions
             * that are part of the Art Blocks Flagship core contract.
             * @author Art Blocks Inc.
             */
            // This interface extends IGenArt721CoreContractV3_Base with functions that are
            // in part of the Art Blocks Flagship core contract.
            interface IGenArt721CoreContractV3 is IGenArt721CoreContractV3_Base {
                // @dev new function in V3
                function getPrimaryRevenueSplits(
                    uint256 _projectId,
                    uint256 _price
                )
                    external
                    view
                    returns (
                        uint256 artblocksRevenue_,
                        address payable artblocksAddress_,
                        uint256 artistRevenue_,
                        address payable artistAddress_,
                        uint256 additionalPayeePrimaryRevenue_,
                        address payable additionalPayeePrimaryAddress_
                    );
                // @dev Art Blocks primary sales payment address
                function artblocksPrimarySalesAddress()
                    external
                    view
                    returns (address payable);
                /**
                 * @notice Backwards-compatible (pre-V3) function returning Art Blocks
                 * primary sales payment address (now called artblocksPrimarySalesAddress).
                 */
                function artblocksAddress() external view returns (address payable);
                // @dev Percentage of primary sales allocated to Art Blocks
                function artblocksPrimarySalesPercentage() external view returns (uint256);
                /**
                 * @notice Backwards-compatible (pre-V3) function returning Art Blocks
                 * primary sales percentage (now called artblocksPrimarySalesPercentage).
                 */
                function artblocksPercentage() external view returns (uint256);
                // @dev Art Blocks secondary sales royalties payment address
                function artblocksSecondarySalesAddress()
                    external
                    view
                    returns (address payable);
                // @dev Basis points of secondary sales allocated to Art Blocks
                function artblocksSecondarySalesBPS() external view returns (uint256);
                /**
                 * @notice Backwards-compatible (pre-V3) function  that gets artist +
                 * artist's additional payee royalty data for token ID `_tokenId`.
                 * WARNING: Does not include Art Blocks portion of royalties.
                 */
                function getRoyaltyData(
                    uint256 _tokenId
                )
                    external
                    view
                    returns (
                        address artistAddress,
                        address additionalPayee,
                        uint256 additionalPayeePercentage,
                        uint256 royaltyFeeByID
                    );
            }
            // SPDX-License-Identifier: MIT
            pragma solidity ^0.8.0;
            /// @dev Royalty Registry interface, used to support the Royalty Registry.
            /// @dev Source: https://github.com/manifoldxyz/royalty-registry-solidity/blob/main/contracts/specs/IManifold.sol
            /// @author: manifold.xyz
            /**
             * @dev Royalty interface for creator core classes
             */
            interface IManifold {
                /**
                 * @dev Get royalites of a token.  Returns list of receivers and basisPoints
                 *
                 *  bytes4(keccak256('getRoyalties(uint256)')) == 0xbb3bafd6
                 *
                 *  => 0xbb3bafd6 = 0xbb3bafd6
                 */
                function getRoyalties(
                    uint256 tokenId
                ) external view returns (address payable[] memory, uint256[] memory);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            import "./IFilteredMinterV2.sol";
            pragma solidity ^0.8.0;
            /**
             * @title This interface defines any events or functions required for a minter
             * to conform to the MinterBase contract.
             * @dev The MinterBase contract was not implemented from the beginning of the
             * MinterSuite contract suite, therefore early versions of some minters may not
             * conform to this interface.
             * @author Art Blocks Inc.
             */
            interface IMinterBaseV0 {
                // Function that returns if a minter is configured to integrate with a V3 flagship or V3 engine contract.
                // Returns true only if the minter is configured to integrate with an engine contract.
                function isEngine() external returns (bool isEngine);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            interface IMinterFilterV0 {
                /**
                 * @notice Emitted when contract is deployed to notify indexing services
                 * of the new contract deployment.
                 */
                event Deployed();
                /**
                 * @notice Approved minter `_minterAddress`.
                 */
                event MinterApproved(address indexed _minterAddress, string _minterType);
                /**
                 * @notice Revoked approval for minter `_minterAddress`
                 */
                event MinterRevoked(address indexed _minterAddress);
                /**
                 * @notice Minter `_minterAddress` of type `_minterType`
                 * registered for project `_projectId`.
                 */
                event ProjectMinterRegistered(
                    uint256 indexed _projectId,
                    address indexed _minterAddress,
                    string _minterType
                );
                /**
                 * @notice Any active minter removed for project `_projectId`.
                 */
                event ProjectMinterRemoved(uint256 indexed _projectId);
                function genArt721CoreAddress() external returns (address);
                function setMinterForProject(uint256, address) external;
                function removeMinterForProject(uint256) external;
                function mint(
                    address _to,
                    uint256 _projectId,
                    address sender
                ) external returns (uint256);
                function getMinterForProject(uint256) external view returns (address);
                function projectHasMinter(uint256) external view returns (bool);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            import "../../interfaces/v0.8.x/IMinterBaseV0.sol";
            import "../../interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol";
            import "../../interfaces/v0.8.x/IGenArt721CoreContractV3.sol";
            import "../../interfaces/v0.8.x/IGenArt721CoreContractV3_Engine.sol";
            import "@openzeppelin-4.7/contracts/token/ERC20/IERC20.sol";
            pragma solidity ^0.8.0;
            /**
             * @title Art Blocks Minter Base Class
             * @notice A base class for Art Blocks minter contracts that provides common
             * functionality used across minter contracts.
             * This contract is not intended to be deployed directly, but rather to be
             * inherited by other minter contracts.
             * From a design perspective, this contract is intended to remain simple and
             * easy to understand. It is not intended to cause a complex inheritance tree,
             * and instead should keep minter contracts as readable as possible for
             * collectors and developers.
             * @dev Semantic versioning is used in the solidity file name, and is therefore
             * controlled by contracts importing the appropriate filename version.
             * @author Art Blocks Inc.
             */
            abstract contract MinterBase is IMinterBaseV0 {
                /// state variable that tracks whether this contract's associated core
                /// contract is an Engine contract, where Engine contracts have an
                /// additional revenue split for the platform provider
                bool public immutable isEngine;
                // @dev we do not track an initialization state, as the only state variable
                // is immutable, which the compiler enforces to be assigned during
                // construction.
                /**
                 * @notice Initializes contract to ensure state variable `isEngine` is set
                 * appropriately based on the minter's associated core contract address.
                 * @param genArt721Address Art Blocks core contract address for
                 * which this contract will be a minter.
                 */
                constructor(address genArt721Address) {
                    // set state variable isEngine
                    isEngine = _getV3CoreIsEngine(genArt721Address);
                }
                /**
                 * @notice splits ETH funds between sender (if refund), providers,
                 * artist, and artist's additional payee for a token purchased on
                 * project `_projectId`.
                 * WARNING: This function uses msg.value and msg.sender to determine
                 * refund amounts, and therefore may not be applicable to all use cases
                 * (e.g. do not use with Dutch Auctions with on-chain settlement).
                 * @dev possible DoS during splits is acknowledged, and mitigated by
                 * business practices, including end-to-end testing on mainnet, and
                 * admin-accepted artist payment addresses.
                 * @param projectId Project ID for which funds shall be split.
                 * @param pricePerTokenInWei Current price of token, in Wei.
                 */
                function splitFundsETH(
                    uint256 projectId,
                    uint256 pricePerTokenInWei,
                    address genArt721CoreAddress
                ) internal {
                    if (msg.value > 0) {
                        bool success_;
                        // send refund to sender
                        uint256 refund = msg.value - pricePerTokenInWei;
                        if (refund > 0) {
                            (success_, ) = msg.sender.call{value: refund}("");
                            require(success_, "Refund failed");
                        }
                        // split revenues
                        splitRevenuesETH(
                            projectId,
                            pricePerTokenInWei,
                            genArt721CoreAddress
                        );
                    }
                }
                /**
                 * @notice splits ETH revenues between providers, artist, and artist's
                 * additional payee for revenue generated by project `_projectId`.
                 * @dev possible DoS during splits is acknowledged, and mitigated by
                 * business practices, including end-to-end testing on mainnet, and
                 * admin-accepted artist payment addresses.
                 * @param projectId Project ID for which funds shall be split.
                 * @param valueInWei Value to be split, in Wei.
                 */
                function splitRevenuesETH(
                    uint256 projectId,
                    uint256 valueInWei,
                    address genArtCoreContract
                ) internal {
                    if (valueInWei <= 0) {
                        return; // return early
                    }
                    bool success;
                    // split funds between platforms, artist, and artist's
                    // additional payee
                    uint256 renderProviderRevenue_;
                    address payable renderProviderAddress_;
                    uint256 artistRevenue_;
                    address payable artistAddress_;
                    uint256 additionalPayeePrimaryRevenue_;
                    address payable additionalPayeePrimaryAddress_;
                    if (isEngine) {
                        // get engine splits
                        uint256 platformProviderRevenue_;
                        address payable platformProviderAddress_;
                        (
                            renderProviderRevenue_,
                            renderProviderAddress_,
                            platformProviderRevenue_,
                            platformProviderAddress_,
                            artistRevenue_,
                            artistAddress_,
                            additionalPayeePrimaryRevenue_,
                            additionalPayeePrimaryAddress_
                        ) = IGenArt721CoreContractV3_Engine(genArtCoreContract)
                            .getPrimaryRevenueSplits(projectId, valueInWei);
                        // Platform Provider payment (only possible if engine)
                        if (platformProviderRevenue_ > 0) {
                            (success, ) = platformProviderAddress_.call{
                                value: platformProviderRevenue_
                            }("");
                            require(success, "Platform Provider payment failed");
                        }
                    } else {
                        // get flagship splits
                        (
                            renderProviderRevenue_, // artblocks revenue
                            renderProviderAddress_, // artblocks address
                            artistRevenue_,
                            artistAddress_,
                            additionalPayeePrimaryRevenue_,
                            additionalPayeePrimaryAddress_
                        ) = IGenArt721CoreContractV3(genArtCoreContract)
                            .getPrimaryRevenueSplits(projectId, valueInWei);
                    }
                    // Render Provider / Art Blocks payment
                    if (renderProviderRevenue_ > 0) {
                        (success, ) = renderProviderAddress_.call{
                            value: renderProviderRevenue_
                        }("");
                        require(success, "Render Provider payment failed");
                    }
                    // artist payment
                    if (artistRevenue_ > 0) {
                        (success, ) = artistAddress_.call{value: artistRevenue_}("");
                        require(success, "Artist payment failed");
                    }
                    // additional payee payment
                    if (additionalPayeePrimaryRevenue_ > 0) {
                        (success, ) = additionalPayeePrimaryAddress_.call{
                            value: additionalPayeePrimaryRevenue_
                        }("");
                        require(success, "Additional Payee payment failed");
                    }
                }
                /**
                 * @notice splits ERC-20 funds between providers, artist, and artist's
                 * additional payee, for a token purchased on project `_projectId`.
                 * @dev possible DoS during splits is acknowledged, and mitigated by
                 * business practices, including end-to-end testing on mainnet, and
                 * admin-accepted artist payment addresses.
                 */
                function splitFundsERC20(
                    uint256 projectId,
                    uint256 pricePerTokenInWei,
                    address currencyAddress,
                    address genArtCoreContract
                ) internal {
                    IERC20 _projectCurrency = IERC20(currencyAddress);
                    // split remaining funds between foundation, artist, and artist's
                    // additional payee
                    uint256 renderProviderRevenue_;
                    address payable renderProviderAddress_;
                    uint256 artistRevenue_;
                    address payable artistAddress_;
                    uint256 additionalPayeePrimaryRevenue_;
                    address payable additionalPayeePrimaryAddress_;
                    if (isEngine) {
                        // get engine splits
                        uint256 platformProviderRevenue_;
                        address payable platformProviderAddress_;
                        (
                            renderProviderRevenue_,
                            renderProviderAddress_,
                            platformProviderRevenue_,
                            platformProviderAddress_,
                            artistRevenue_,
                            artistAddress_,
                            additionalPayeePrimaryRevenue_,
                            additionalPayeePrimaryAddress_
                        ) = IGenArt721CoreContractV3_Engine(genArtCoreContract)
                            .getPrimaryRevenueSplits(projectId, pricePerTokenInWei);
                        // Platform Provider payment (only possible if engine)
                        if (platformProviderRevenue_ > 0) {
                            _projectCurrency.transferFrom(
                                msg.sender,
                                platformProviderAddress_,
                                platformProviderRevenue_
                            );
                        }
                    } else {
                        // get flagship splits
                        (
                            renderProviderRevenue_, // artblocks revenue
                            renderProviderAddress_, // artblocks address
                            artistRevenue_,
                            artistAddress_,
                            additionalPayeePrimaryRevenue_,
                            additionalPayeePrimaryAddress_
                        ) = IGenArt721CoreContractV3(genArtCoreContract)
                            .getPrimaryRevenueSplits(projectId, pricePerTokenInWei);
                    }
                    // Art Blocks payment
                    if (renderProviderRevenue_ > 0) {
                        _projectCurrency.transferFrom(
                            msg.sender,
                            renderProviderAddress_,
                            renderProviderRevenue_
                        );
                    }
                    // artist payment
                    if (artistRevenue_ > 0) {
                        _projectCurrency.transferFrom(
                            msg.sender,
                            artistAddress_,
                            artistRevenue_
                        );
                    }
                    // additional payee payment
                    if (additionalPayeePrimaryRevenue_ > 0) {
                        _projectCurrency.transferFrom(
                            msg.sender,
                            additionalPayeePrimaryAddress_,
                            additionalPayeePrimaryRevenue_
                        );
                    }
                }
                /**
                 * @notice Returns whether a V3 core contract is an Art Blocks Engine
                 * contract or not. Return value of false indicates that the core is a
                 * flagship contract.
                 * @dev this function reverts if a core contract does not return the
                 * expected number of return values from getPrimaryRevenueSplits() for
                 * either a flagship or engine core contract.
                 * @dev this function uses the length of the return data (in bytes) to
                 * determine whether the core is an engine or not.
                 * @param genArt721CoreV3 The address of the deployed core contract.
                 */
                function _getV3CoreIsEngine(
                    address genArt721CoreV3
                ) private returns (bool) {
                    // call getPrimaryRevenueSplits() on core contract
                    bytes memory payload = abi.encodeWithSignature(
                        "getPrimaryRevenueSplits(uint256,uint256)",
                        0,
                        0
                    );
                    (bool success, bytes memory returnData) = genArt721CoreV3.call(payload);
                    require(success, "getPrimaryRevenueSplits() call failed");
                    // determine whether core is engine or not, based on return data length
                    uint256 returnDataLength = returnData.length;
                    if (returnDataLength == 6 * 32) {
                        // 6 32-byte words returned if flagship (not engine)
                        // @dev 6 32-byte words are expected because the non-engine core
                        // contracts return a payout address and uint256 payment value for
                        // the artist, and artist's additional payee, and Art Blocks.
                        // also note that per Solidity ABI encoding, the address return
                        // values are padded to 32 bytes.
                        return false;
                    } else if (returnDataLength == 8 * 32) {
                        // 8 32-byte words returned if engine
                        // @dev 8 32-byte words are expected because the engine core
                        // contracts return a payout address and uint256 payment value for
                        // the artist, artist's additional payee, render provider
                        // typically Art Blocks, and platform provider (partner).
                        // also note that per Solidity ABI encoding, the address return
                        // values are padded to 32 bytes.
                        return true;
                    } else {
                        // unexpected return value length
                        revert("Unexpected revenue split bytes");
                    }
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            import "../../interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol";
            import "../../interfaces/v0.8.x/IMinterFilterV0.sol";
            import "../../interfaces/v0.8.x/IFilteredMinterV2.sol";
            import "./MinterBase_v0_1_1.sol";
            import "@openzeppelin-4.5/contracts/security/ReentrancyGuard.sol";
            pragma solidity 0.8.19;
            /**
             * @title Filtered Minter contract that allows tokens to be minted with ETH.
             * This is designed to be used with GenArt721CoreContractV3 flagship or
             * engine contracts.
             * @author Art Blocks Inc.
             * @notice Privileged Roles and Ownership:
             * This contract is designed to be managed, with limited powers.
             * Privileged roles and abilities are controlled by the project's artist, which
             * can be modified by the core contract's Admin ACL contract. Both of these
             * roles hold extensive power and can modify minter details.
             * Care must be taken to ensure that the admin ACL contract and artist
             * addresses are secure behind a multi-sig or other access control mechanism.
             * ----------------------------------------------------------------------------
             * The following functions are restricted to a project's artist:
             * - updatePricePerTokenInWei
             * - setProjectMaxInvocations
             * - manuallyLimitProjectMaxInvocations
             * ----------------------------------------------------------------------------
             * Additional admin and artist privileged roles may be described on other
             * contracts that this minter integrates with.
             */
            contract MinterSetPriceV4 is ReentrancyGuard, MinterBase, IFilteredMinterV2 {
                /// Core contract address this minter interacts with
                address public immutable genArt721CoreAddress;
                /// The core contract integrates with V3 contracts
                IGenArt721CoreContractV3_Base private immutable genArtCoreContract_Base;
                /// Minter filter address this minter interacts with
                address public immutable minterFilterAddress;
                /// Minter filter this minter may interact with.
                IMinterFilterV0 private immutable minterFilter;
                /// minterType for this minter
                string public constant minterType = "MinterSetPriceV4";
                /// minter version for this minter
                string public constant minterVersion = "v4.1.0";
                uint256 constant ONE_MILLION = 1_000_000;
                struct ProjectConfig {
                    bool maxHasBeenInvoked;
                    bool priceIsConfigured;
                    uint24 maxInvocations;
                    uint256 pricePerTokenInWei;
                }
                mapping(uint256 => ProjectConfig) public projectConfig;
                function _onlyArtist(uint256 _projectId) internal view {
                    require(
                        msg.sender ==
                            genArtCoreContract_Base.projectIdToArtistAddress(_projectId),
                        "Only Artist"
                    );
                }
                /**
                 * @notice Initializes contract to be a Filtered Minter for
                 * `_minterFilter`, integrated with Art Blocks core contract
                 * at address `_genArt721Address`.
                 * @param _genArt721Address Art Blocks core contract address for
                 * which this contract will be a minter.
                 * @param _minterFilter Minter filter for which this will be a
                 * filtered minter.
                 */
                constructor(
                    address _genArt721Address,
                    address _minterFilter
                ) ReentrancyGuard() MinterBase(_genArt721Address) {
                    genArt721CoreAddress = _genArt721Address;
                    // always populate immutable engine contracts, but only use appropriate
                    // interface based on isEngine in the rest of the contract
                    genArtCoreContract_Base = IGenArt721CoreContractV3_Base(
                        _genArt721Address
                    );
                    minterFilterAddress = _minterFilter;
                    minterFilter = IMinterFilterV0(_minterFilter);
                    require(
                        minterFilter.genArt721CoreAddress() == _genArt721Address,
                        "Illegal contract pairing"
                    );
                }
                /**
                 * @notice Syncs local maximum invocations of project `_projectId` based on
                 * the value currently defined in the core contract.
                 * @param _projectId Project ID to set the maximum invocations for.
                 * @dev this enables gas reduction after maxInvocations have been reached -
                 * core contracts shall still enforce a maxInvocation check during mint.
                 */
                function setProjectMaxInvocations(uint256 _projectId) public {
                    _onlyArtist(_projectId);
                    uint256 maxInvocations;
                    uint256 invocations;
                    (invocations, maxInvocations, , , , ) = genArtCoreContract_Base
                        .projectStateData(_projectId);
                    // update storage with results
                    projectConfig[_projectId].maxInvocations = uint24(maxInvocations);
                    // We need to ensure maxHasBeenInvoked is correctly set after manually syncing the
                    // local maxInvocations value with the core contract's maxInvocations value.
                    // This synced value of maxInvocations from the core contract will always be greater
                    // than or equal to the previous value of maxInvocations stored locally.
                    projectConfig[_projectId].maxHasBeenInvoked =
                        invocations == maxInvocations;
                    emit ProjectMaxInvocationsLimitUpdated(_projectId, maxInvocations);
                }
                /**
                 * @notice Manually sets the local maximum invocations of project `_projectId`
                 * with the provided `_maxInvocations`, checking that `_maxInvocations` is less
                 * than or equal to the value of project `_project_id`'s maximum invocations that is
                 * set on the core contract.
                 * @dev Note that a `_maxInvocations` of 0 can only be set if the current `invocations`
                 * value is also 0 and this would also set `maxHasBeenInvoked` to true, correctly short-circuiting
                 * this minter's purchase function, avoiding extra gas costs from the core contract's maxInvocations check.
                 * @param _projectId Project ID to set the maximum invocations for.
                 * @param _maxInvocations Maximum invocations to set for the project.
                 */
                function manuallyLimitProjectMaxInvocations(
                    uint256 _projectId,
                    uint256 _maxInvocations
                ) external {
                    _onlyArtist(_projectId);
                    // CHECKS
                    // ensure that the manually set maxInvocations is not greater than what is set on the core contract
                    uint256 maxInvocations;
                    uint256 invocations;
                    (invocations, maxInvocations, , , , ) = genArtCoreContract_Base
                        .projectStateData(_projectId);
                    require(
                        _maxInvocations <= maxInvocations,
                        "Cannot increase project max invocations above core contract set project max invocations"
                    );
                    require(
                        _maxInvocations >= invocations,
                        "Cannot set project max invocations to less than current invocations"
                    );
                    // EFFECTS
                    // update storage with results
                    projectConfig[_projectId].maxInvocations = uint24(_maxInvocations);
                    // We need to ensure maxHasBeenInvoked is correctly set after manually setting the
                    // local maxInvocations value.
                    projectConfig[_projectId].maxHasBeenInvoked =
                        invocations == _maxInvocations;
                    emit ProjectMaxInvocationsLimitUpdated(_projectId, _maxInvocations);
                }
                /**
                 * @notice Warning: Disabling purchaseTo is not supported on this minter.
                 * This method exists purely for interface-conformance purposes.
                 */
                function togglePurchaseToDisabled(uint256 _projectId) external view {
                    _onlyArtist(_projectId);
                    revert("Action not supported");
                }
                /**
                 * @notice projectId => has project reached its maximum number of
                 * invocations? Note that this returns a local cache of the core contract's
                 * state, and may be out of sync with the core contract. This is
                 * intentional, as it only enables gas optimization of mints after a
                 * project's maximum invocations has been reached. A false negative will
                 * only result in a gas cost increase, since the core contract will still
                 * enforce a maxInvocation check during minting. A false positive is not
                 * possible because the V3 core contract only allows maximum invocations
                 * to be reduced, not increased. Based on this rationale, we intentionally
                 * do not do input validation in this method as to whether or not the input
                 * `_projectId` is an existing project ID.
                 */
                function projectMaxHasBeenInvoked(
                    uint256 _projectId
                ) external view returns (bool) {
                    return projectConfig[_projectId].maxHasBeenInvoked;
                }
                /**
                 * @notice projectId => project's maximum number of invocations.
                 * Optionally synced with core contract value, for gas optimization.
                 * Note that this returns a local cache of the core contract's
                 * state, and may be out of sync with the core contract. This is
                 * intentional, as it only enables gas optimization of mints after a
                 * project's maximum invocations has been reached.
                 * @dev A number greater than the core contract's project max invocations
                 * will only result in a gas cost increase, since the core contract will
                 * still enforce a maxInvocation check during minting. A number less than
                 * the core contract's project max invocations is only possible when the
                 * project's max invocations have not been synced on this minter, since the
                 * V3 core contract only allows maximum invocations to be reduced, not
                 * increased. When this happens, the minter will enable minting, allowing
                 * the core contract to enforce the max invocations check. Based on this
                 * rationale, we intentionally do not do input validation in this method as
                 * to whether or not the input `_projectId` is an existing project ID.
                 */
                function projectMaxInvocations(
                    uint256 _projectId
                ) external view returns (uint256) {
                    return uint256(projectConfig[_projectId].maxInvocations);
                }
                /**
                 * @notice Updates this minter's price per token of project `_projectId`
                 * to be '_pricePerTokenInWei`, in Wei.
                 * This price supersedes any legacy core contract price per token value.
                 * @dev Note that it is intentionally supported here that the configured
                 * price may be explicitly set to `0`.
                 */
                function updatePricePerTokenInWei(
                    uint256 _projectId,
                    uint256 _pricePerTokenInWei
                ) external {
                    _onlyArtist(_projectId);
                    ProjectConfig storage _projectConfig = projectConfig[_projectId];
                    _projectConfig.pricePerTokenInWei = _pricePerTokenInWei;
                    _projectConfig.priceIsConfigured = true;
                    emit PricePerTokenInWeiUpdated(_projectId, _pricePerTokenInWei);
                    // sync local max invocations if not initially populated
                    // @dev if local max invocations and maxHasBeenInvoked are both
                    // initial values, we know they have not been populated.
                    if (
                        _projectConfig.maxInvocations == 0 &&
                        _projectConfig.maxHasBeenInvoked == false
                    ) {
                        setProjectMaxInvocations(_projectId);
                    }
                }
                /**
                 * @notice Purchases a token from project `_projectId`.
                 * @param _projectId Project ID to mint a token on.
                 * @return tokenId Token ID of minted token
                 */
                function purchase(
                    uint256 _projectId
                ) external payable returns (uint256 tokenId) {
                    tokenId = purchaseTo_do6(msg.sender, _projectId);
                    return tokenId;
                }
                /**
                 * @notice gas-optimized version of purchase(uint256).
                 */
                function purchase_H4M(
                    uint256 _projectId
                ) external payable returns (uint256 tokenId) {
                    tokenId = purchaseTo_do6(msg.sender, _projectId);
                    return tokenId;
                }
                /**
                 * @notice Purchases a token from project `_projectId` and sets
                 * the token's owner to `_to`.
                 * @param _to Address to be the new token's owner.
                 * @param _projectId Project ID to mint a token on.
                 * @return tokenId Token ID of minted token
                 */
                function purchaseTo(
                    address _to,
                    uint256 _projectId
                ) external payable returns (uint256 tokenId) {
                    return purchaseTo_do6(_to, _projectId);
                }
                /**
                 * @notice gas-optimized version of purchaseTo(address, uint256).
                 */
                function purchaseTo_do6(
                    address _to,
                    uint256 _projectId
                ) public payable nonReentrant returns (uint256 tokenId) {
                    // CHECKS
                    ProjectConfig storage _projectConfig = projectConfig[_projectId];
                    // Note that `maxHasBeenInvoked` is only checked here to reduce gas
                    // consumption after a project has been fully minted.
                    // `_projectConfig.maxHasBeenInvoked` is locally cached to reduce
                    // gas consumption, but if not in sync with the core contract's value,
                    // the core contract also enforces its own max invocation check during
                    // minting.
                    require(
                        !_projectConfig.maxHasBeenInvoked,
                        "Maximum number of invocations reached"
                    );
                    // require artist to have configured price of token on this minter
                    require(_projectConfig.priceIsConfigured, "Price not configured");
                    // load price of token into memory
                    uint256 pricePerTokenInWei = _projectConfig.pricePerTokenInWei;
                    require(
                        msg.value >= pricePerTokenInWei,
                        "Must send minimum value to mint!"
                    );
                    // EFFECTS
                    tokenId = minterFilter.mint(_to, _projectId, msg.sender);
                    // invocation is token number plus one, and will never overflow due to
                    // limit of 1e6 invocations per project. block scope for gas efficiency
                    // (i.e. avoid an unnecessary var initialization to 0).
                    unchecked {
                        uint256 tokenInvocation = (tokenId % ONE_MILLION) + 1;
                        uint256 localMaxInvocations = _projectConfig.maxInvocations;
                        // handle the case where the token invocation == minter local max
                        // invocations occurred on a different minter, and we have a stale
                        // local maxHasBeenInvoked value returning a false negative.
                        // @dev this is a CHECK after EFFECTS, so security was considered
                        // in detail here.
                        require(
                            tokenInvocation <= localMaxInvocations,
                            "Maximum invocations reached"
                        );
                        // in typical case, update the local maxHasBeenInvoked value
                        // to true if the token invocation == minter local max invocations
                        // (enables gas efficient reverts after sellout)
                        if (tokenInvocation == localMaxInvocations) {
                            _projectConfig.maxHasBeenInvoked = true;
                        }
                    }
                    // INTERACTIONS
                    splitFundsETH(_projectId, pricePerTokenInWei, genArt721CoreAddress);
                    return tokenId;
                }
                /**
                 * @notice Gets if price of token is configured, price of minting a
                 * token on project `_projectId`, and currency symbol and address to be
                 * used as payment. Supersedes any core contract price information.
                 * @param _projectId Project ID to get price information for.
                 * @return isConfigured true only if token price has been configured on
                 * this minter
                 * @return tokenPriceInWei current price of token on this minter - invalid
                 * if price has not yet been configured
                 * @return currencySymbol currency symbol for purchases of project on this
                 * minter. This minter always returns "ETH"
                 * @return currencyAddress currency address for purchases of project on
                 * this minter. This minter always returns null address, reserved for ether
                 */
                function getPriceInfo(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        bool isConfigured,
                        uint256 tokenPriceInWei,
                        string memory currencySymbol,
                        address currencyAddress
                    )
                {
                    ProjectConfig storage _projectConfig = projectConfig[_projectId];
                    isConfigured = _projectConfig.priceIsConfigured;
                    tokenPriceInWei = _projectConfig.pricePerTokenInWei;
                    currencySymbol = "ETH";
                    currencyAddress = address(0);
                }
            }
            

            File 2 of 5: GenArt721CoreV3_Engine_Flex
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
            pragma solidity ^0.8.0;
            import "../utils/Context.sol";
            /**
             * @dev Contract module which provides a basic access control mechanism, where
             * there is an account (an owner) that can be granted exclusive access to
             * specific functions.
             *
             * By default, the owner account will be the one that deploys the contract. This
             * can later be changed with {transferOwnership}.
             *
             * This module is used through inheritance. It will make available the modifier
             * `onlyOwner`, which can be applied to your functions to restrict their use to
             * the owner.
             */
            abstract contract Ownable is Context {
                address private _owner;
                event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                /**
                 * @dev Initializes the contract setting the deployer as the initial owner.
                 */
                constructor() {
                    _transferOwnership(_msgSender());
                }
                /**
                 * @dev Throws if called by any account other than the owner.
                 */
                modifier onlyOwner() {
                    _checkOwner();
                    _;
                }
                /**
                 * @dev Returns the address of the current owner.
                 */
                function owner() public view virtual returns (address) {
                    return _owner;
                }
                /**
                 * @dev Throws if the sender is not the owner.
                 */
                function _checkOwner() internal view virtual {
                    require(owner() == _msgSender(), "Ownable: caller is not the owner");
                }
                /**
                 * @dev Leaves the contract without owner. It will not be possible to call
                 * `onlyOwner` functions anymore. Can only be called by the current owner.
                 *
                 * NOTE: Renouncing ownership will leave the contract without an owner,
                 * thereby removing any functionality that is only available to the owner.
                 */
                function renounceOwnership() public virtual onlyOwner {
                    _transferOwnership(address(0));
                }
                /**
                 * @dev Transfers ownership of the contract to a new account (`newOwner`).
                 * Can only be called by the current owner.
                 */
                function transferOwnership(address newOwner) public virtual onlyOwner {
                    require(newOwner != address(0), "Ownable: new owner is the zero address");
                    _transferOwnership(newOwner);
                }
                /**
                 * @dev Transfers ownership of the contract to a new account (`newOwner`).
                 * Internal function without access restriction.
                 */
                function _transferOwnership(address newOwner) internal virtual {
                    address oldOwner = _owner;
                    _owner = newOwner;
                    emit OwnershipTransferred(oldOwner, newOwner);
                }
            }
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
            pragma solidity ^0.8.0;
            import "../IERC721.sol";
            /**
             * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
             * @dev See https://eips.ethereum.org/EIPS/eip-721
             */
            interface IERC721Metadata is IERC721 {
                /**
                 * @dev Returns the token collection name.
                 */
                function name() external view returns (string memory);
                /**
                 * @dev Returns the token collection symbol.
                 */
                function symbol() external view returns (string memory);
                /**
                 * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
                 */
                function tokenURI(uint256 tokenId) external view returns (string memory);
            }
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)
            pragma solidity ^0.8.0;
            import "../../utils/introspection/IERC165.sol";
            /**
             * @dev Required interface of an ERC721 compliant contract.
             */
            interface IERC721 is IERC165 {
                /**
                 * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
                 */
                event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
                /**
                 * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
                 */
                event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
                /**
                 * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
                 */
                event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
                /**
                 * @dev Returns the number of tokens in ``owner``'s account.
                 */
                function balanceOf(address owner) external view returns (uint256 balance);
                /**
                 * @dev Returns the owner of the `tokenId` token.
                 *
                 * Requirements:
                 *
                 * - `tokenId` must exist.
                 */
                function ownerOf(uint256 tokenId) external view returns (address owner);
                /**
                 * @dev Safely transfers `tokenId` token from `from` to `to`.
                 *
                 * Requirements:
                 *
                 * - `from` cannot be the zero address.
                 * - `to` cannot be the zero address.
                 * - `tokenId` token must exist and be owned by `from`.
                 * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
                 * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
                 *
                 * Emits a {Transfer} event.
                 */
                function safeTransferFrom(
                    address from,
                    address to,
                    uint256 tokenId,
                    bytes calldata data
                ) external;
                /**
                 * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
                 * are aware of the ERC721 protocol to prevent tokens from being forever locked.
                 *
                 * Requirements:
                 *
                 * - `from` cannot be the zero address.
                 * - `to` cannot be the zero address.
                 * - `tokenId` token must exist and be owned by `from`.
                 * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
                 * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
                 *
                 * Emits a {Transfer} event.
                 */
                function safeTransferFrom(
                    address from,
                    address to,
                    uint256 tokenId
                ) external;
                /**
                 * @dev Transfers `tokenId` token from `from` to `to`.
                 *
                 * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
                 *
                 * Requirements:
                 *
                 * - `from` cannot be the zero address.
                 * - `to` cannot be the zero address.
                 * - `tokenId` token must be owned by `from`.
                 * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
                 *
                 * Emits a {Transfer} event.
                 */
                function transferFrom(
                    address from,
                    address to,
                    uint256 tokenId
                ) external;
                /**
                 * @dev Gives permission to `to` to transfer `tokenId` token to another account.
                 * The approval is cleared when the token is transferred.
                 *
                 * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
                 *
                 * Requirements:
                 *
                 * - The caller must own the token or be an approved operator.
                 * - `tokenId` must exist.
                 *
                 * Emits an {Approval} event.
                 */
                function approve(address to, uint256 tokenId) external;
                /**
                 * @dev Approve or remove `operator` as an operator for the caller.
                 * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
                 *
                 * Requirements:
                 *
                 * - The `operator` cannot be the caller.
                 *
                 * Emits an {ApprovalForAll} event.
                 */
                function setApprovalForAll(address operator, bool _approved) external;
                /**
                 * @dev Returns the account approved for `tokenId` token.
                 *
                 * Requirements:
                 *
                 * - `tokenId` must exist.
                 */
                function getApproved(uint256 tokenId) external view returns (address operator);
                /**
                 * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
                 *
                 * See {setApprovalForAll}
                 */
                function isApprovedForAll(address owner, address operator) external view returns (bool);
            }
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
            pragma solidity ^0.8.0;
            /**
             * @title ERC721 token receiver interface
             * @dev Interface for any contract that wants to support safeTransfers
             * from ERC721 asset contracts.
             */
            interface IERC721Receiver {
                /**
                 * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
                 * by `operator` from `from`, this function is called.
                 *
                 * It must return its Solidity selector to confirm the token transfer.
                 * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
                 *
                 * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
                 */
                function onERC721Received(
                    address operator,
                    address from,
                    uint256 tokenId,
                    bytes calldata data
                ) external returns (bytes4);
            }
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)
            pragma solidity ^0.8.1;
            /**
             * @dev Collection of functions related to the address type
             */
            library Address {
                /**
                 * @dev Returns true if `account` is a contract.
                 *
                 * [IMPORTANT]
                 * ====
                 * It is unsafe to assume that an address for which this function returns
                 * false is an externally-owned account (EOA) and not a contract.
                 *
                 * Among others, `isContract` will return false for the following
                 * types of addresses:
                 *
                 *  - an externally-owned account
                 *  - a contract in construction
                 *  - an address where a contract will be created
                 *  - an address where a contract lived, but was destroyed
                 * ====
                 *
                 * [IMPORTANT]
                 * ====
                 * You shouldn't rely on `isContract` to protect against flash loan attacks!
                 *
                 * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
                 * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
                 * constructor.
                 * ====
                 */
                function isContract(address account) internal view returns (bool) {
                    // This method relies on extcodesize/address.code.length, which returns 0
                    // for contracts in construction, since the code is only stored at the end
                    // of the constructor execution.
                    return account.code.length > 0;
                }
                /**
                 * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                 * `recipient`, forwarding all available gas and reverting on errors.
                 *
                 * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                 * of certain opcodes, possibly making contracts go over the 2300 gas limit
                 * imposed by `transfer`, making them unable to receive funds via
                 * `transfer`. {sendValue} removes this limitation.
                 *
                 * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                 *
                 * IMPORTANT: because control is transferred to `recipient`, care must be
                 * taken to not create reentrancy vulnerabilities. Consider using
                 * {ReentrancyGuard} or the
                 * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                 */
                function sendValue(address payable recipient, uint256 amount) internal {
                    require(address(this).balance >= amount, "Address: insufficient balance");
                    (bool success, ) = recipient.call{value: amount}("");
                    require(success, "Address: unable to send value, recipient may have reverted");
                }
                /**
                 * @dev Performs a Solidity function call using a low level `call`. A
                 * plain `call` is an unsafe replacement for a function call: use this
                 * function instead.
                 *
                 * If `target` reverts with a revert reason, it is bubbled up by this
                 * function (like regular Solidity function calls).
                 *
                 * Returns the raw returned data. To convert to the expected return value,
                 * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                 *
                 * Requirements:
                 *
                 * - `target` must be a contract.
                 * - calling `target` with `data` must not revert.
                 *
                 * _Available since v3.1._
                 */
                function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                    return functionCall(target, data, "Address: low-level call failed");
                }
                /**
                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                 * `errorMessage` as a fallback revert reason when `target` reverts.
                 *
                 * _Available since v3.1._
                 */
                function functionCall(
                    address target,
                    bytes memory data,
                    string memory errorMessage
                ) internal returns (bytes memory) {
                    return functionCallWithValue(target, data, 0, errorMessage);
                }
                /**
                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                 * but also transferring `value` wei to `target`.
                 *
                 * Requirements:
                 *
                 * - the calling contract must have an ETH balance of at least `value`.
                 * - the called Solidity function must be `payable`.
                 *
                 * _Available since v3.1._
                 */
                function functionCallWithValue(
                    address target,
                    bytes memory data,
                    uint256 value
                ) internal returns (bytes memory) {
                    return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                }
                /**
                 * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                 * with `errorMessage` as a fallback revert reason when `target` reverts.
                 *
                 * _Available since v3.1._
                 */
                function functionCallWithValue(
                    address target,
                    bytes memory data,
                    uint256 value,
                    string memory errorMessage
                ) internal returns (bytes memory) {
                    require(address(this).balance >= value, "Address: insufficient balance for call");
                    require(isContract(target), "Address: call to non-contract");
                    (bool success, bytes memory returndata) = target.call{value: value}(data);
                    return verifyCallResult(success, returndata, errorMessage);
                }
                /**
                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                 * but performing a static call.
                 *
                 * _Available since v3.3._
                 */
                function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                    return functionStaticCall(target, data, "Address: low-level static call failed");
                }
                /**
                 * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                 * but performing a static call.
                 *
                 * _Available since v3.3._
                 */
                function functionStaticCall(
                    address target,
                    bytes memory data,
                    string memory errorMessage
                ) internal view returns (bytes memory) {
                    require(isContract(target), "Address: static call to non-contract");
                    (bool success, bytes memory returndata) = target.staticcall(data);
                    return verifyCallResult(success, returndata, errorMessage);
                }
                /**
                 * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                 * but performing a delegate call.
                 *
                 * _Available since v3.4._
                 */
                function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                    return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                }
                /**
                 * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                 * but performing a delegate call.
                 *
                 * _Available since v3.4._
                 */
                function functionDelegateCall(
                    address target,
                    bytes memory data,
                    string memory errorMessage
                ) internal returns (bytes memory) {
                    require(isContract(target), "Address: delegate call to non-contract");
                    (bool success, bytes memory returndata) = target.delegatecall(data);
                    return verifyCallResult(success, returndata, errorMessage);
                }
                /**
                 * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
                 * revert reason using the provided one.
                 *
                 * _Available since v4.3._
                 */
                function verifyCallResult(
                    bool success,
                    bytes memory returndata,
                    string memory errorMessage
                ) internal pure returns (bytes memory) {
                    if (success) {
                        return returndata;
                    } else {
                        // Look for revert reason and bubble it up if present
                        if (returndata.length > 0) {
                            // The easiest way to bubble the revert reason is using memory via assembly
                            /// @solidity memory-safe-assembly
                            assembly {
                                let returndata_size := mload(returndata)
                                revert(add(32, returndata), returndata_size)
                            }
                        } else {
                            revert(errorMessage);
                        }
                    }
                }
            }
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
            pragma solidity ^0.8.0;
            /**
             * @dev Provides information about the current execution context, including the
             * sender of the transaction and its data. While these are generally available
             * via msg.sender and msg.data, they should not be accessed in such a direct
             * manner, since when dealing with meta-transactions the account sending and
             * paying for execution may not be the actual sender (as far as an application
             * is concerned).
             *
             * This contract is only required for intermediate, library-like contracts.
             */
            abstract contract Context {
                function _msgSender() internal view virtual returns (address) {
                    return msg.sender;
                }
                function _msgData() internal view virtual returns (bytes calldata) {
                    return msg.data;
                }
            }
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
            pragma solidity ^0.8.0;
            import "./IERC165.sol";
            /**
             * @dev Implementation of the {IERC165} interface.
             *
             * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
             * for the additional interface id that will be supported. For example:
             *
             * ```solidity
             * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
             *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
             * }
             * ```
             *
             * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
             */
            abstract contract ERC165 is IERC165 {
                /**
                 * @dev See {IERC165-supportsInterface}.
                 */
                function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                    return interfaceId == type(IERC165).interfaceId;
                }
            }
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
            pragma solidity ^0.8.0;
            /**
             * @dev Interface of the ERC165 standard, as defined in the
             * https://eips.ethereum.org/EIPS/eip-165[EIP].
             *
             * Implementers can declare support of contract interfaces, which can then be
             * queried by others ({ERC165Checker}).
             *
             * For an implementation, see {ERC165}.
             */
            interface IERC165 {
                /**
                 * @dev Returns true if this contract implements the interface defined by
                 * `interfaceId`. See the corresponding
                 * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
                 * to learn more about how these ids are created.
                 *
                 * This function call must use less than 30 000 gas.
                 */
                function supportsInterface(bytes4 interfaceId) external view returns (bool);
            }
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
            pragma solidity ^0.8.0;
            /**
             * @dev String operations.
             */
            library Strings {
                bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
                uint8 private constant _ADDRESS_LENGTH = 20;
                /**
                 * @dev Converts a `uint256` to its ASCII `string` decimal representation.
                 */
                function toString(uint256 value) internal pure returns (string memory) {
                    // Inspired by OraclizeAPI's implementation - MIT licence
                    // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
                    if (value == 0) {
                        return "0";
                    }
                    uint256 temp = value;
                    uint256 digits;
                    while (temp != 0) {
                        digits++;
                        temp /= 10;
                    }
                    bytes memory buffer = new bytes(digits);
                    while (value != 0) {
                        digits -= 1;
                        buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                        value /= 10;
                    }
                    return string(buffer);
                }
                /**
                 * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
                 */
                function toHexString(uint256 value) internal pure returns (string memory) {
                    if (value == 0) {
                        return "0x00";
                    }
                    uint256 temp = value;
                    uint256 length = 0;
                    while (temp != 0) {
                        length++;
                        temp >>= 8;
                    }
                    return toHexString(value, length);
                }
                /**
                 * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
                 */
                function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                    bytes memory buffer = new bytes(2 * length + 2);
                    buffer[0] = "0";
                    buffer[1] = "x";
                    for (uint256 i = 2 * length + 1; i > 1; --i) {
                        buffer[i] = _HEX_SYMBOLS[value & 0xf];
                        value >>= 4;
                    }
                    require(value == 0, "Strings: hex length insufficient");
                    return string(buffer);
                }
                /**
                 * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
                 */
                function toHexString(address addr) internal pure returns (string memory) {
                    return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity 0.8.19;
            // Created By: Art Blocks Inc.
            import "../../interfaces/v0.8.x/IRandomizer_V3CoreBase.sol";
            import "../../interfaces/v0.8.x/IAdminACLV0.sol";
            import "../../interfaces/v0.8.x/IGenArt721CoreContractV3_Engine_Flex.sol";
            import "../../interfaces/v0.8.x/IGenArt721CoreContractExposesHashSeed.sol";
            import "../../interfaces/v0.8.x/IDependencyRegistryCompatibleV0.sol";
            import "../../interfaces/v0.8.x/IManifold.sol";
            import "@openzeppelin-4.7/contracts/access/Ownable.sol";
            import "../../libs/v0.8.x/ERC721_PackedHashSeed.sol";
            import "../../libs/v0.8.x/BytecodeStorageV1.sol";
            import "../../libs/v0.8.x/Bytes32Strings.sol";
            /**
             * @title Art Blocks Engine Flex ERC-721 core contract, V3.
             * @author Art Blocks Inc.
             * @notice Privileged Roles and Ownership:
             * This contract is designed to be managed, with progressively limited powers
             * as a project progresses from active to locked.
             * Privileged roles and abilities are controlled by the admin ACL contract and
             * artists. Both of these roles hold extensive power and can arbitrarily
             * control and modify portions of projects, dependent upon project state. After
             * a project is locked, important project metadata fields are locked including
             * the project name, artist name, and script and display details. Edition size
             * can never be increased.
             * Care must be taken to ensure that the admin ACL contract and artist
             * addresses are secure behind a multi-sig or other access control mechanism.
             * ----------------------------------------------------------------------------
             * The following functions are restricted to the Admin ACL contract:
             * - updateArtblocksDependencyRegistryAddress
             * - updateProviderSalesAddresses
             * - updateProviderPrimarySalesPercentages (up to 100%)
             * - updateProviderSecondarySalesBPS (up to 100%)
             * - updateMinterContract
             * - updateRandomizerAddress
             * - toggleProjectIsActive
             * - addProject
             * - forbidNewProjects (forever forbidding new projects)
             * - updateDefaultBaseURI (used to initialize new project base URIs)
             * - updateIPFSGateway
             * - updateArweaveGateway
             * ----------------------------------------------------------------------------
             * The following functions are restricted to either the the Artist address or
             * the Admin ACL contract, only when the project is not locked:
             * - updateProjectName
             * - updateProjectArtistName
             * - updateProjectLicense
             * - Change project script via addProjectScript, updateProjectScript,
             *   and removeProjectLastScript
             * - updateProjectScriptType
             * - updateProjectAspectRatio
             * ----------------------------------------------------------------------------
             * The following functions are restricted to only the Artist address:
             * - proposeArtistPaymentAddressesAndSplits (Note that this has to be accepted
             *   by adminAcceptArtistAddressesAndSplits to take effect, which is restricted
             *   to the Admin ACL contract, or the artist if the core contract owner has
             *   renounced ownership. Also note that a proposal will be automatically
             *   accepted if the artist only proposes changed payee percentages without
             *   modifying any payee addresses, or is only removing payee addresses, or
             *   if the global config `autoApproveArtistSplitProposals` is set to `true`.)
             * - toggleProjectIsPaused (note the artist can still mint while paused)
             * - updateProjectSecondaryMarketRoyaltyPercentage (up to
                 ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent)
             * - updateProjectWebsite
             * - updateProjectMaxInvocations (to a number greater than or equal to the
             *   current number of invocations, and less than current project maximum
             *   invocations)
             * - updateProjectBaseURI (controlling the base URI for tokens in the project)
             * ----------------------------------------------------------------------------
             * The following function is restricted to either the Admin ACL contract, or
             * the Artist address if the core contract owner has renounced ownership:
             * - adminAcceptArtistAddressesAndSplits
             * - updateProjectArtistAddress (owner ultimately controlling the project and
             *   its and-on revenue, unless owner has renounced ownership)
             * ----------------------------------------------------------------------------
             * The following function is restricted to the artist when a project is
             * unlocked, and only callable by Admin ACL contract when a project is locked:
             * - updateProjectDescription
             * ----------------------------------------------------------------------------
             * The following functions for managing external asset dependencies are restricted
             * to projects with external asset dependencies that are unlocked:
             * - lockProjectExternalAssetDependencies 
             * - updateProjectExternalAssetDependency
             * - removeProjectExternalAssetDependency
             * - addProjectExternalAssetDependency
             * ----------------------------------------------------------------------------
             * The following function is restricted to owner calling directly:
             * - transferOwnership
             * - renounceOwnership
             * ----------------------------------------------------------------------------
             * The following configuration variables are set at time of contract deployment,
             * and not modifiable thereafter (immutable after the point of deployment):
             * - (bool) autoApproveArtistSplitProposals
             * ----------------------------------------------------------------------------
             * Additional admin and artist privileged roles may be described on minters,
             * registries, and other contracts that may interact with this core contract.
             */
            contract GenArt721CoreV3_Engine_Flex is
                ERC721_PackedHashSeed,
                Ownable,
                IDependencyRegistryCompatibleV0,
                IManifold,
                IGenArt721CoreContractV3_Engine_Flex,
                IGenArt721CoreContractExposesHashSeed
            {
                using BytecodeStorageWriter for string;
                using Bytes32Strings for bytes32;
                uint256 constant ONE_HUNDRED = 100;
                uint256 constant ONE_MILLION = 1_000_000;
                uint24 constant ONE_MILLION_UINT24 = 1_000_000;
                uint256 constant FOUR_WEEKS_IN_SECONDS = 2_419_200;
                uint8 constant AT_CHARACTER_CODE = uint8(bytes1("@")); // 0x40
                // numeric constants
                uint256 constant MAX_PROVIDER_SECONDARY_SALES_BPS = 10000; // 10_000 BPS = 100%
                uint256 constant ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE = 95; // 95%
                // This contract emits generic events that contain fields that indicate
                // which parameter has been updated. This is sufficient for application
                // state management, while also simplifying the contract and indexing code.
                // This was done as an alternative to having custom events that emit what
                // field-values have changed for each event, given that changed values can
                // be introspected by indexers due to the design of this smart contract
                // exposing these state changes via publicly viewable fields.
                //
                // The following fields are used to indicate which contract-level parameter
                // has been updated in the `PlatformUpdated` event:
                bytes32 constant FIELD_NEXT_PROJECT_ID = "nextProjectId";
                bytes32 constant FIELD_NEW_PROJECTS_FORBIDDEN = "newProjectsForbidden";
                bytes32 constant FIELD_DEFAULT_BASE_URI = "defaultBaseURI";
                bytes32 constant FIELD_RANDOMIZER_ADDRESS = "randomizerAddress";
                bytes32 constant FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS =
                    "dependencyRegistryAddress";
                bytes32 constant FIELD_PROVIDER_SALES_ADDRESSES = "providerSalesAddresses";
                bytes32 constant FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES =
                    "providerPrimaryPercentages";
                bytes32 constant FIELD_PROVIDER_SECONDARY_SALES_BPS =
                    "providerSecondaryBPS";
                // The following fields are used to indicate which project-level parameter
                // has been updated in the `ProjectUpdated` event:
                bytes32 constant FIELD_PROJECT_COMPLETED = "completed";
                bytes32 constant FIELD_PROJECT_ACTIVE = "active";
                bytes32 constant FIELD_PROJECT_ARTIST_ADDRESS = "artistAddress";
                bytes32 constant FIELD_PROJECT_PAUSED = "paused";
                bytes32 constant FIELD_PROJECT_CREATED = "created";
                bytes32 constant FIELD_PROJECT_NAME = "name";
                bytes32 constant FIELD_PROJECT_ARTIST_NAME = "artistName";
                bytes32 constant FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE =
                    "royaltyPercentage";
                bytes32 constant FIELD_PROJECT_DESCRIPTION = "description";
                bytes32 constant FIELD_PROJECT_WEBSITE = "website";
                bytes32 constant FIELD_PROJECT_LICENSE = "license";
                bytes32 constant FIELD_PROJECT_MAX_INVOCATIONS = "maxInvocations";
                bytes32 constant FIELD_PROJECT_SCRIPT = "script";
                bytes32 constant FIELD_PROJECT_SCRIPT_TYPE = "scriptType";
                bytes32 constant FIELD_PROJECT_ASPECT_RATIO = "aspectRatio";
                bytes32 constant FIELD_PROJECT_BASE_URI = "baseURI";
                /// Dependency registry managed by Art Blocks
                address public artblocksDependencyRegistryAddress;
                /// current randomizer contract
                IRandomizer_V3CoreBase public randomizerContract;
                /// append-only array of all randomizer contract addresses ever used by
                /// this contract
                address[] private _historicalRandomizerAddresses;
                /// admin ACL contract
                IAdminACLV0 public adminACLContract;
                struct Project {
                    uint24 invocations;
                    uint24 maxInvocations;
                    uint24 scriptCount;
                    // max uint64 ~= 1.8e19 sec ~= 570 billion years
                    uint64 completedTimestamp;
                    bool active;
                    bool paused;
                    string name;
                    string artist;
                    address descriptionAddress;
                    string website;
                    string license;
                    string projectBaseURI;
                    bytes32 scriptTypeAndVersion;
                    string aspectRatio;
                    // mapping from script index to address storing script in bytecode
                    mapping(uint256 => address) scriptBytecodeAddresses;
                    bool externalAssetDependenciesLocked;
                    uint24 externalAssetDependencyCount;
                    mapping(uint256 => ExternalAssetDependency) externalAssetDependencies;
                }
                mapping(uint256 => Project) projects;
                string public preferredIPFSGateway;
                string public preferredArweaveGateway;
                /// packed struct containing project financial information
                struct ProjectFinance {
                    address payable additionalPayeePrimarySales;
                    // packed uint: max of 95, max uint8 = 255
                    uint8 secondaryMarketRoyaltyPercentage;
                    address payable additionalPayeeSecondarySales;
                    // packed uint: max of 100, max uint8 = 255
                    uint8 additionalPayeeSecondarySalesPercentage;
                    address payable artistAddress;
                    // packed uint: max of 100, max uint8 = 255
                    uint8 additionalPayeePrimarySalesPercentage;
                }
                // Project financials mapping
                mapping(uint256 => ProjectFinance) projectIdToFinancials;
                /// hash of artist's proposed payment updates to be approved by admin
                mapping(uint256 => bytes32) public proposedArtistAddressesAndSplitsHash;
                /// The render provider payment address for all primary sales revenues
                /// (packed)
                address payable public renderProviderPrimarySalesAddress;
                /// Percentage of primary sales revenue allocated to the render provider
                /// (packed)
                // packed uint: max of 100, max uint8 = 255
                uint8 private _renderProviderPrimarySalesPercentage = 10;
                /// The platform provider payment address for all primary sales revenues
                /// (packed)
                address payable public platformProviderPrimarySalesAddress;
                /// Percentage of primary sales revenue allocated to the platform provider
                /// (packed)
                // packed uint: max of 100, max uint8 = 255
                uint8 private _platformProviderPrimarySalesPercentage = 10;
                /// The render provider payment address for all secondary sales royalty
                /// revenues
                address payable public renderProviderSecondarySalesAddress;
                /// Basis Points of secondary sales royalties allocated to the
                /// render provider
                uint256 public renderProviderSecondarySalesBPS = 250;
                /// The platform provider payment address for all secondary sales royalty
                /// revenues
                address payable public platformProviderSecondarySalesAddress;
                /// Basis Points of secondary sales royalties allocated to the
                /// platform provider
                uint256 public platformProviderSecondarySalesBPS = 250;
                /// single minter allowed for this core contract
                address public minterContract;
                /// starting (initial) project ID on this contract
                uint256 public immutable startingProjectId;
                /// next project ID to be created
                uint248 private _nextProjectId;
                /// bool indicating if adding new projects is forbidden;
                /// default behavior is to allow new projects
                bool public newProjectsForbidden;
                /// configuration variable (determined at time of deployment)
                /// that determines whether or not admin approval^ should be required
                /// to accept artist address change proposals, or if these proposals
                /// should always auto-approve, as determined by the business process
                /// requirements of the Engine partner using this contract.
                ///
                /// ^does not apply in the case where contract-ownership itself is revoked
                bool public immutable autoApproveArtistSplitProposals;
                /// version & type of this core contract
                bytes32 constant CORE_VERSION = "v3.1.4";
                function coreVersion() external pure returns (string memory) {
                    return CORE_VERSION.toString();
                }
                bytes32 constant CORE_TYPE = "GenArt721CoreV3_Engine_Flex";
                function coreType() external pure returns (string memory) {
                    return CORE_TYPE.toString();
                }
                /// default base URI to initialize all new project projectBaseURI values to
                string public defaultBaseURI;
                function _onlyUnlockedProjectExternalAssetDependencies(
                    uint256 _projectId
                ) internal view {
                    require(
                        !projects[_projectId].externalAssetDependenciesLocked,
                        "External dependencies locked"
                    );
                }
                function _onlyNonZeroAddress(address _address) internal pure {
                    require(_address != address(0), "Must input non-zero address");
                }
                function _onlyNonEmptyString(string memory _string) internal pure {
                    require(bytes(_string).length != 0, "Must input non-empty string");
                }
                function _onlyValidTokenId(uint256 _tokenId) internal view {
                    require(_exists(_tokenId), "Token ID does not exist");
                }
                function _onlyValidProjectId(uint256 _projectId) internal view {
                    require(
                        (_projectId >= startingProjectId) && (_projectId < _nextProjectId),
                        "Project ID does not exist"
                    );
                }
                function _onlyUnlocked(uint256 _projectId) internal view {
                    // Note: calling `_projectUnlocked` enforces that the `_projectId`
                    //       passed in is valid.`
                    require(_projectUnlocked(_projectId), "Only if unlocked");
                }
                function _onlyAdminACL(bytes4 _selector) internal {
                    require(
                        adminACLAllowed(msg.sender, address(this), _selector),
                        "Only Admin ACL allowed"
                    );
                }
                function _onlyArtist(uint256 _projectId) internal view {
                    require(
                        msg.sender == projectIdToFinancials[_projectId].artistAddress,
                        "Only artist"
                    );
                }
                function _onlyArtistOrAdminACL(
                    uint256 _projectId,
                    bytes4 _selector
                ) internal {
                    require(
                        msg.sender == projectIdToFinancials[_projectId].artistAddress ||
                            adminACLAllowed(msg.sender, address(this), _selector),
                        "Only artist or Admin ACL allowed"
                    );
                }
                /**
                 * This modifier allows the artist of a project to call a function if the
                 * owner of the contract has renounced ownership. This is to allow the
                 * contract to continue to function if the owner decides to renounce
                 * ownership.
                 */
                function _onlyAdminACLOrRenouncedArtist(
                    uint256 _projectId,
                    bytes4 _selector
                ) internal {
                    require(
                        adminACLAllowed(msg.sender, address(this), _selector) ||
                            (owner() == address(0) &&
                                msg.sender ==
                                projectIdToFinancials[_projectId].artistAddress),
                        "Only Admin ACL allowed, or artist if owner has renounced"
                    );
                }
                /**
                 * @notice Initializes contract.
                 * @param _tokenName Name of token.
                 * @param _tokenSymbol Token symbol.
                 * @param _randomizerContract Randomizer contract.
                 * @param _adminACLContract Address of admin access control contract, to be
                 * set as contract owner.
                 * @param _startingProjectId The initial next project ID.
                 * @param _autoApproveArtistSplitProposals Whether or not to always
                 * auto-approve proposed artist split updates.
                 * @dev _startingProjectId should be set to a value much, much less than
                 * max(uint248), but an explicit input type of `uint248` is used as it is
                 * safer to cast up to `uint256` than it is to cast down for the purposes
                 * of setting `_nextProjectId`.
                 */
                constructor(
                    string memory _tokenName,
                    string memory _tokenSymbol,
                    address _renderProviderAddress,
                    address _platformProviderAddress,
                    address _randomizerContract,
                    address _adminACLContract,
                    uint248 _startingProjectId,
                    bool _autoApproveArtistSplitProposals
                ) ERC721_PackedHashSeed(_tokenName, _tokenSymbol) {
                    _onlyNonZeroAddress(_renderProviderAddress);
                    _onlyNonZeroAddress(_platformProviderAddress);
                    _onlyNonZeroAddress(_randomizerContract);
                    _onlyNonZeroAddress(_adminACLContract);
                    // setup immutable `autoApproveArtistSplitProposals` config
                    autoApproveArtistSplitProposals = _autoApproveArtistSplitProposals;
                    // record contracts starting project ID
                    // casting-up is safe
                    startingProjectId = uint256(_startingProjectId);
                    _updateProviderSalesAddresses(
                        _renderProviderAddress,
                        _renderProviderAddress,
                        _platformProviderAddress,
                        _platformProviderAddress
                    );
                    _updateRandomizerAddress(_randomizerContract);
                    // set AdminACL management contract as owner
                    _transferOwnership(_adminACLContract);
                    // initialize default base URI
                    _updateDefaultBaseURI(
                        string.concat(
                            "https://token.artblocks.io/",
                            toHexString(address(this)),
                            "/"
                        )
                    );
                    // initialize next project ID
                    _nextProjectId = _startingProjectId;
                    emit PlatformUpdated(FIELD_NEXT_PROJECT_ID);
                    // @dev follow-on action: This contract does not self-register. A core
                    // registry owner must register contract in a subsequent call.
                }
                /**
                 * @notice Updates preferredIPFSGateway to `_gateway`.
                 */
                function updateIPFSGateway(string calldata _gateway) public {
                    _onlyAdminACL(this.updateIPFSGateway.selector);
                    preferredIPFSGateway = _gateway;
                    emit GatewayUpdated(ExternalAssetDependencyType.IPFS, _gateway);
                }
                /**
                 * @notice Updates preferredArweaveGateway to `_gateway`.
                 */
                function updateArweaveGateway(string calldata _gateway) public {
                    _onlyAdminACL(this.updateArweaveGateway.selector);
                    preferredArweaveGateway = _gateway;
                    emit GatewayUpdated(ExternalAssetDependencyType.ARWEAVE, _gateway);
                }
                /**
                 * @notice Locks external asset dependencies for project `_projectId`.
                 */
                function lockProjectExternalAssetDependencies(uint256 _projectId) external {
                    _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                    _onlyArtistOrAdminACL(
                        _projectId,
                        this.lockProjectExternalAssetDependencies.selector
                    );
                    projects[_projectId].externalAssetDependenciesLocked = true;
                    emit ProjectExternalAssetDependenciesLocked(_projectId);
                }
                /**
                 * @notice Updates external asset dependency for project `_projectId`.
                 * @param _projectId Project to be updated.
                 * @param _index Asset index.
                 * @param _cidOrData Asset cid (Content identifier) or data string to be translated into bytecode.
                 * @param _dependencyType Asset dependency type.
                 *  0 - IPFS
                 *  1 - ARWEAVE
                 *  2 - ONCHAIN
                 */
                function updateProjectExternalAssetDependency(
                    uint256 _projectId,
                    uint256 _index,
                    string memory _cidOrData,
                    ExternalAssetDependencyType _dependencyType
                ) external {
                    _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                    _onlyArtistOrAdminACL(
                        _projectId,
                        this.updateProjectExternalAssetDependency.selector
                    );
                    uint24 assetCount = projects[_projectId].externalAssetDependencyCount;
                    require(_index < assetCount, "Asset index out of range");
                    ExternalAssetDependency storage _oldDependency = projects[_projectId]
                        .externalAssetDependencies[_index];
                    ExternalAssetDependencyType _oldDependencyType = _oldDependency
                        .dependencyType;
                    projects[_projectId]
                        .externalAssetDependencies[_index]
                        .dependencyType = _dependencyType;
                    // if the incoming dependency type is onchain, we need to write the data to bytecode
                    if (_dependencyType == ExternalAssetDependencyType.ONCHAIN) {
                        if (_oldDependencyType != ExternalAssetDependencyType.ONCHAIN) {
                            // we only need to set the cid to an empty string if we are replacing an offchain asset
                            // an onchain asset will already have an empty cid
                            projects[_projectId].externalAssetDependencies[_index].cid = "";
                        }
                        projects[_projectId]
                            .externalAssetDependencies[_index]
                            .bytecodeAddress = _cidOrData.writeToBytecode();
                        // we don't want to emit data, so we emit the cid as an empty string
                        _cidOrData = "";
                    } else {
                        projects[_projectId]
                            .externalAssetDependencies[_index]
                            .cid = _cidOrData;
                    }
                    emit ExternalAssetDependencyUpdated(
                        _projectId,
                        _index,
                        _cidOrData,
                        _dependencyType,
                        assetCount
                    );
                }
                /**
                 * @notice Removes external asset dependency for project `_projectId` at index `_index`.
                 * Removal is done by swapping the element to be removed with the last element in the array, then deleting this last element.
                 * Assets with indices higher than `_index` can have their indices adjusted as a result of this operation.
                 * @param _projectId Project to be updated.
                 * @param _index Asset index
                 */
                function removeProjectExternalAssetDependency(
                    uint256 _projectId,
                    uint256 _index
                ) external {
                    _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                    _onlyArtistOrAdminACL(
                        _projectId,
                        this.removeProjectExternalAssetDependency.selector
                    );
                    uint24 assetCount = projects[_projectId].externalAssetDependencyCount;
                    require(_index < assetCount, "Asset index out of range");
                    uint24 lastElementIndex = assetCount - 1;
                    // copy last element to index of element to be removed
                    projects[_projectId].externalAssetDependencies[_index] = projects[
                        _projectId
                    ].externalAssetDependencies[lastElementIndex];
                    delete projects[_projectId].externalAssetDependencies[lastElementIndex];
                    projects[_projectId].externalAssetDependencyCount = lastElementIndex;
                    emit ExternalAssetDependencyRemoved(_projectId, _index);
                }
                /**
                 * @notice Adds external asset dependency for project `_projectId`.
                 * @param _projectId Project to be updated.
                 * @param _cidOrData Asset cid (Content identifier) or data string to be translated into bytecode.
                 * @param _dependencyType Asset dependency type.
                 *  0 - IPFS
                 *  1 - ARWEAVE
                 *  2 - ONCHAIN
                 */
                function addProjectExternalAssetDependency(
                    uint256 _projectId,
                    string memory _cidOrData,
                    ExternalAssetDependencyType _dependencyType
                ) external {
                    _onlyUnlockedProjectExternalAssetDependencies(_projectId);
                    _onlyArtistOrAdminACL(
                        _projectId,
                        this.addProjectExternalAssetDependency.selector
                    );
                    uint24 assetCount = projects[_projectId].externalAssetDependencyCount;
                    address _bytecodeAddress = address(0);
                    // if the incoming dependency type is onchain, we need to write the data to bytecode
                    if (_dependencyType == ExternalAssetDependencyType.ONCHAIN) {
                        _bytecodeAddress = _cidOrData.writeToBytecode();
                        // we don't want to emit data, so we emit the cid as an empty string
                        _cidOrData = "";
                    }
                    ExternalAssetDependency memory asset = ExternalAssetDependency({
                        cid: _cidOrData,
                        dependencyType: _dependencyType,
                        bytecodeAddress: _bytecodeAddress
                    });
                    projects[_projectId].externalAssetDependencies[assetCount] = asset;
                    projects[_projectId].externalAssetDependencyCount = assetCount + 1;
                    emit ExternalAssetDependencyUpdated(
                        _projectId,
                        assetCount,
                        _cidOrData,
                        _dependencyType,
                        assetCount + 1
                    );
                }
                /**
                 * @notice Mints a token from project `_projectId` and sets the
                 * token's owner to `_to`. Hash may or may not be assigned to the token
                 * during the mint transaction, depending on the randomizer contract.
                 * @param _to Address to be the minted token's owner.
                 * @param _projectId Project ID to mint a token on.
                 * @param _by Purchaser of minted token.
                 * @return _tokenId The ID of the minted token.
                 * @dev sender must be the allowed minterContract
                 * @dev name of function is optimized for gas usage
                 */
                function mint_Ecf(
                    address _to,
                    uint256 _projectId,
                    address _by
                ) external returns (uint256 _tokenId) {
                    // CHECKS
                    require(msg.sender == minterContract, "Must mint from minter contract");
                    Project storage project = projects[_projectId];
                    // load invocations into memory
                    uint24 invocationsBefore = project.invocations;
                    uint24 invocationsAfter;
                    unchecked {
                        // invocationsBefore guaranteed <= maxInvocations <= 1_000_000,
                        // 1_000_000 << max uint24, so no possible overflow
                        invocationsAfter = invocationsBefore + 1;
                    }
                    uint24 maxInvocations = project.maxInvocations;
                    require(
                        invocationsBefore < maxInvocations,
                        "Must not exceed max invocations"
                    );
                    require(
                        project.active ||
                            _by == projectIdToFinancials[_projectId].artistAddress,
                        "Project must exist and be active"
                    );
                    require(
                        !project.paused ||
                            _by == projectIdToFinancials[_projectId].artistAddress,
                        "Purchases are paused."
                    );
                    // EFFECTS
                    // increment project's invocations
                    project.invocations = invocationsAfter;
                    uint256 thisTokenId;
                    unchecked {
                        // invocationsBefore is uint24 << max uint256. In production use,
                        // _projectId * ONE_MILLION must be << max uint256, otherwise
                        // tokenIdToProjectId function become invalid.
                        // Therefore, no risk of overflow
                        thisTokenId = (_projectId * ONE_MILLION) + invocationsBefore;
                    }
                    // mark project as completed if hit max invocations
                    if (invocationsAfter == maxInvocations) {
                        _completeProject(_projectId);
                    }
                    // INTERACTIONS
                    _mint(_to, thisTokenId);
                    // token hash is updated by the randomizer contract on V3
                    randomizerContract.assignTokenHash(thisTokenId);
                    // Do not need to also log `projectId` in event, as the `projectId` for
                    // a given token can be derived from the `tokenId` with:
                    //   projectId = tokenId / 1_000_000
                    emit Mint(_to, thisTokenId);
                    return thisTokenId;
                }
                /**
                 * @notice Sets the hash seed for a given token ID `_tokenId`.
                 * May only be called by the current randomizer contract.
                 * May only be called for tokens that have not already been assigned a
                 * non-zero hash.
                 * @param _tokenId Token ID to set the hash for.
                 * @param _hashSeed Hash seed to set for the token ID. Only last 12 bytes
                 * will be used.
                 * @dev gas-optimized function name because called during mint sequence
                 * @dev if a separate event is required when the token hash is set, e.g.
                 * for indexing purposes, it must be emitted by the randomizer. This is to
                 * minimize gas when minting.
                 */
                function setTokenHash_8PT(uint256 _tokenId, bytes32 _hashSeed) external {
                    _onlyValidTokenId(_tokenId);
                    OwnerAndHashSeed storage ownerAndHashSeed = _ownersAndHashSeeds[
                        _tokenId
                    ];
                    require(
                        msg.sender == address(randomizerContract),
                        "Only randomizer may set"
                    );
                    require(
                        ownerAndHashSeed.hashSeed == bytes12(0),
                        "Token hash already set"
                    );
                    require(_hashSeed != bytes12(0), "No zero hash seed");
                    ownerAndHashSeed.hashSeed = bytes12(_hashSeed);
                }
                /**
                 * @notice Allows owner (AdminACL) to revoke ownership of the contract.
                 * Note that the contract is intended to continue to function after the
                 * owner renounces ownership, but no new projects will be able to be added.
                 * Renouncing ownership will leave the contract without an owner,
                 * thereby removing any functionality that is only available to the
                 * owner/AdminACL contract. The same is true for any dependent contracts
                 * that also integrate with the owner/AdminACL contract (e.g. potentially
                 * minter suite contracts, registry contracts, etc.).
                 * After renouncing ownership, artists will be in control of updates to
                 * their payment addresses and splits (see modifier
                 * onlyAdminACLOrRenouncedArtist`).
                 * While there is no currently intended reason to call this method based on
                 * typical Engine partner business practices, this method exists to allow
                 * artists to continue to maintain the limited set of contract
                 * functionality that exists post-project-lock in an environment in which
                 * there is no longer an admin maintaining this smart contract.
                 * @dev This function is intended to be called directly by the AdminACL,
                 * not by an address allowed by the AdminACL contract.
                 */
                function renounceOwnership() public override onlyOwner {
                    // broadcast that new projects are no longer allowed (if not already)
                    _forbidNewProjects();
                    // renounce ownership viw Ownable
                    Ownable.renounceOwnership();
                }
                /**
                 * @notice Updates reference to Art Blocks Dependency Registry contract.
                 * @param _artblocksDependencyRegistryAddress Address of new Dependency
                 * Registry.
                 */
                function updateArtblocksDependencyRegistryAddress(
                    address _artblocksDependencyRegistryAddress
                ) external {
                    _onlyAdminACL(this.updateArtblocksDependencyRegistryAddress.selector);
                    _onlyNonZeroAddress(_artblocksDependencyRegistryAddress);
                    artblocksDependencyRegistryAddress = _artblocksDependencyRegistryAddress;
                    emit PlatformUpdated(FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS);
                }
                /**
                 * @notice Updates sales addresses for the platform and render providers to
                 * the input parameters.
                 * @param _renderProviderPrimarySalesAddress Address of new primary sales
                 * payment address.
                 * @param _renderProviderSecondarySalesAddress Address of new secondary sales
                 * payment address.
                 * @param _platformProviderPrimarySalesAddress Address of new primary sales
                 * payment address.
                 * @param _platformProviderSecondarySalesAddress Address of new secondary sales
                 * payment address.
                 */
                function updateProviderSalesAddresses(
                    address payable _renderProviderPrimarySalesAddress,
                    address payable _renderProviderSecondarySalesAddress,
                    address payable _platformProviderPrimarySalesAddress,
                    address payable _platformProviderSecondarySalesAddress
                ) external {
                    _onlyAdminACL(this.updateProviderSalesAddresses.selector);
                    _onlyNonZeroAddress(_renderProviderPrimarySalesAddress);
                    _onlyNonZeroAddress(_renderProviderSecondarySalesAddress);
                    _onlyNonZeroAddress(_platformProviderPrimarySalesAddress);
                    _onlyNonZeroAddress(_platformProviderSecondarySalesAddress);
                    _updateProviderSalesAddresses(
                        _renderProviderPrimarySalesAddress,
                        _renderProviderSecondarySalesAddress,
                        _platformProviderPrimarySalesAddress,
                        _platformProviderSecondarySalesAddress
                    );
                }
                /**
                 * @notice Updates the render and platform provider primary sales revenue percentage to
                 * the provided inputs.
                 * @param renderProviderPrimarySalesPercentage_ New primary sales revenue % for the render provider
                 * @param platformProviderPrimarySalesPercentage_ New primary sales revenue % for the platform provider
                 * percentage.
                 */
                function updateProviderPrimarySalesPercentages(
                    uint256 renderProviderPrimarySalesPercentage_,
                    uint256 platformProviderPrimarySalesPercentage_
                ) external {
                    _onlyAdminACL(this.updateProviderPrimarySalesPercentages.selector);
                    // Validate that the sum of the proposed %s, does not exceed 100%.
                    require(
                        (renderProviderPrimarySalesPercentage_ +
                            platformProviderPrimarySalesPercentage_) <= ONE_HUNDRED,
                        "Max sum of ONE_HUNDRED %"
                    );
                    // Casting to `uint8` here is safe due check above, which does not allow
                    // overflow as of solidity version ^0.8.0.
                    _renderProviderPrimarySalesPercentage = uint8(
                        renderProviderPrimarySalesPercentage_
                    );
                    _platformProviderPrimarySalesPercentage = uint8(
                        platformProviderPrimarySalesPercentage_
                    );
                    emit PlatformUpdated(FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES);
                }
                /**
                 * @notice Updates render and platform provider secondary sales royalty Basis Points to
                 * the provided inputs.
                 * @param _renderProviderSecondarySalesBPS New secondary sales royalty Basis
                 * points.
                 * @param _platformProviderSecondarySalesBPS New secondary sales royalty Basis
                 * points.
                 * @dev Due to secondary royalties being ultimately enforced via social
                 * consensus, no hard upper limit is imposed on the BPS value, other than
                 * <= 100% royalty, which would not make mathematical sense. Realistically,
                 * changing this value is expected to either never occur, or be a rare
                 * occurrence.
                 */
                function updateProviderSecondarySalesBPS(
                    uint256 _renderProviderSecondarySalesBPS,
                    uint256 _platformProviderSecondarySalesBPS
                ) external {
                    _onlyAdminACL(this.updateProviderSecondarySalesBPS.selector);
                    // Validate that the sum of the proposed provider BPS, does not exceed 10_000 BPS.
                    require(
                        (_renderProviderSecondarySalesBPS +
                            _platformProviderSecondarySalesBPS) <=
                            MAX_PROVIDER_SECONDARY_SALES_BPS,
                        "Over max sum of BPS"
                    );
                    renderProviderSecondarySalesBPS = _renderProviderSecondarySalesBPS;
                    platformProviderSecondarySalesBPS = _platformProviderSecondarySalesBPS;
                    emit PlatformUpdated(FIELD_PROVIDER_SECONDARY_SALES_BPS);
                }
                /**
                 * @notice Updates minter to `_address`.
                 * @param _address Address of new minter.
                 */
                function updateMinterContract(address _address) external {
                    _onlyAdminACL(this.updateMinterContract.selector);
                    _onlyNonZeroAddress(_address);
                    minterContract = _address;
                    emit MinterUpdated(_address);
                }
                /**
                 * @notice Updates randomizer to `_randomizerAddress`.
                 * @param _randomizerAddress Address of new randomizer.
                 */
                function updateRandomizerAddress(address _randomizerAddress) external {
                    _onlyAdminACL(this.updateRandomizerAddress.selector);
                    _onlyNonZeroAddress(_randomizerAddress);
                    _updateRandomizerAddress(_randomizerAddress);
                }
                /**
                 * @notice Toggles project `_projectId` as active/inactive.
                 * @param _projectId Project ID to be toggled.
                 */
                function toggleProjectIsActive(uint256 _projectId) external {
                    _onlyAdminACL(this.toggleProjectIsActive.selector);
                    _onlyValidProjectId(_projectId);
                    projects[_projectId].active = !projects[_projectId].active;
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_ACTIVE);
                }
                /**
                 * @notice Artist proposes updated set of artist address, additional payee
                 * addresses, and percentage splits for project `_projectId`. Addresses and
                 * percentages do not have to all be changed, but they must all be defined
                 * as a complete set.
                 * Note that if the artist is only proposing a change to the payee percentage
                 * splits, without modifying the payee addresses, the proposal will be
                 * automatically approved and the new splits will become active immediately.
                 * Automatic approval will also be granted if the artist is only removing
                 * additional payee addresses, without adding any new ones.
                 * Also note that if `autoApproveArtistSplitProposals` is true, proposals
                 * will always be auto-approved, regardless of what is being changed.
                 * Also note that if the artist is proposing sending funds to the zero
                 * address, this function will revert and the proposal will not be created.
                 * @param _projectId Project ID.
                 * @param _artistAddress Artist address that controls the project, and may
                 * receive payments.
                 * @param _additionalPayeePrimarySales Address that may receive a
                 * percentage split of the artist's primary sales revenue.
                 * @param _additionalPayeePrimarySalesPercentage Percent of artist's
                 * portion of primary sale revenue that will be split to address
                 * `_additionalPayeePrimarySales`.
                 * @param _additionalPayeeSecondarySales Address that may receive a percentage
                 * split of the secondary sales royalties.
                 * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion
                 * of secondary sale royalties that will be split to address
                 * `_additionalPayeeSecondarySales`.
                 * @dev `_artistAddress` must be a valid address (non-zero-address), but it
                 * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`
                 * and their associated percentages to be zero'd out by the controlling artist.
                 */
                function proposeArtistPaymentAddressesAndSplits(
                    uint256 _projectId,
                    address payable _artistAddress,
                    address payable _additionalPayeePrimarySales,
                    uint256 _additionalPayeePrimarySalesPercentage,
                    address payable _additionalPayeeSecondarySales,
                    uint256 _additionalPayeeSecondarySalesPercentage
                ) external {
                    _onlyValidProjectId(_projectId);
                    _onlyArtist(_projectId);
                    _onlyNonZeroAddress(_artistAddress);
                    ProjectFinance storage projectFinance = projectIdToFinancials[
                        _projectId
                    ];
                    // checks
                    require(
                        _additionalPayeePrimarySalesPercentage <= ONE_HUNDRED &&
                            _additionalPayeeSecondarySalesPercentage <= ONE_HUNDRED,
                        "Max of 100%"
                    );
                    require(
                        _additionalPayeePrimarySalesPercentage == 0 ||
                            _additionalPayeePrimarySales != address(0),
                        "Primary payee is zero address"
                    );
                    require(
                        _additionalPayeeSecondarySalesPercentage == 0 ||
                            _additionalPayeeSecondarySales != address(0),
                        "Secondary payee is zero address"
                    );
                    // effects
                    // emit event for off-chain indexing
                    // note: always emit a proposal event, even in the pathway of
                    // automatic approval, to simplify indexing expectations
                    emit ProposedArtistAddressesAndSplits(
                        _projectId,
                        _artistAddress,
                        _additionalPayeePrimarySales,
                        _additionalPayeePrimarySalesPercentage,
                        _additionalPayeeSecondarySales,
                        _additionalPayeeSecondarySalesPercentage
                    );
                    // automatically accept if no proposed addresses modifications, or if
                    // the proposal only removes payee addresses, or if contract is set to
                    // always auto-approve.
                    // store proposal hash on-chain, only if not automatic accept
                    bool automaticAccept = autoApproveArtistSplitProposals;
                    if (!automaticAccept) {
                        // block scope to avoid stack too deep error
                        bool artistUnchanged = _artistAddress ==
                            projectFinance.artistAddress;
                        bool additionalPrimaryUnchangedOrRemoved = (_additionalPayeePrimarySales ==
                                projectFinance.additionalPayeePrimarySales) ||
                                (_additionalPayeePrimarySales == address(0));
                        bool additionalSecondaryUnchangedOrRemoved = (_additionalPayeeSecondarySales ==
                                projectFinance.additionalPayeeSecondarySales) ||
                                (_additionalPayeeSecondarySales == address(0));
                        automaticAccept =
                            artistUnchanged &&
                            additionalPrimaryUnchangedOrRemoved &&
                            additionalSecondaryUnchangedOrRemoved;
                    }
                    if (automaticAccept) {
                        // clear any previously proposed values
                        proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);
                        // update storage
                        // artist address can change during automatic accept if
                        // autoApproveArtistSplitProposals is true
                        projectFinance.artistAddress = _artistAddress;
                        projectFinance
                            .additionalPayeePrimarySales = _additionalPayeePrimarySales;
                        // safe to cast as uint8 as max is 100%, max uint8 is 255
                        projectFinance.additionalPayeePrimarySalesPercentage = uint8(
                            _additionalPayeePrimarySalesPercentage
                        );
                        projectFinance
                            .additionalPayeeSecondarySales = _additionalPayeeSecondarySales;
                        // safe to cast as uint8 as max is 100%, max uint8 is 255
                        projectFinance.additionalPayeeSecondarySalesPercentage = uint8(
                            _additionalPayeeSecondarySalesPercentage
                        );
                        // emit event for off-chain indexing
                        emit AcceptedArtistAddressesAndSplits(_projectId);
                    } else {
                        proposedArtistAddressesAndSplitsHash[_projectId] = keccak256(
                            abi.encode(
                                _artistAddress,
                                _additionalPayeePrimarySales,
                                _additionalPayeePrimarySalesPercentage,
                                _additionalPayeeSecondarySales,
                                _additionalPayeeSecondarySalesPercentage
                            )
                        );
                    }
                }
                /**
                 * @notice Admin accepts a proposed set of updated artist address,
                 * additional payee addresses, and percentage splits for project
                 * `_projectId`. Addresses and percentages do not have to all be changed,
                 * but they must all be defined as a complete set.
                 * @param _projectId Project ID.
                 * @param _artistAddress Artist address that controls the project, and may
                 * receive payments.
                 * @param _additionalPayeePrimarySales Address that may receive a
                 * percentage split of the artist's primary sales revenue.
                 * @param _additionalPayeePrimarySalesPercentage Percent of artist's
                 * portion of primary sale revenue that will be split to address
                 * `_additionalPayeePrimarySales`.
                 * @param _additionalPayeeSecondarySales Address that may receive a percentage
                 * split of the secondary sales royalties.
                 * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion
                 * of secondary sale royalties that will be split to address
                 * `_additionalPayeeSecondarySales`.
                 * @dev this must be called by the Admin ACL contract, and must only accept
                 * the most recent proposed values for a given project (validated on-chain
                 * by comparing the hash of the proposed and accepted values).
                 * @dev `_artistAddress` must be a valid address (non-zero-address), but it
                 * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`
                 * and their associated percentages to be zero'd out by the controlling artist.
                 */
                function adminAcceptArtistAddressesAndSplits(
                    uint256 _projectId,
                    address payable _artistAddress,
                    address payable _additionalPayeePrimarySales,
                    uint256 _additionalPayeePrimarySalesPercentage,
                    address payable _additionalPayeeSecondarySales,
                    uint256 _additionalPayeeSecondarySalesPercentage
                ) external {
                    _onlyValidProjectId(_projectId);
                    _onlyAdminACLOrRenouncedArtist(
                        _projectId,
                        this.adminAcceptArtistAddressesAndSplits.selector
                    );
                    _onlyNonZeroAddress(_artistAddress);
                    // checks
                    require(
                        proposedArtistAddressesAndSplitsHash[_projectId] ==
                            keccak256(
                                abi.encode(
                                    _artistAddress,
                                    _additionalPayeePrimarySales,
                                    _additionalPayeePrimarySalesPercentage,
                                    _additionalPayeeSecondarySales,
                                    _additionalPayeeSecondarySalesPercentage
                                )
                            ),
                        "Must match artist proposal"
                    );
                    // effects
                    ProjectFinance storage projectFinance = projectIdToFinancials[
                        _projectId
                    ];
                    projectFinance.artistAddress = _artistAddress;
                    projectFinance
                        .additionalPayeePrimarySales = _additionalPayeePrimarySales;
                    projectFinance.additionalPayeePrimarySalesPercentage = uint8(
                        _additionalPayeePrimarySalesPercentage
                    );
                    projectFinance
                        .additionalPayeeSecondarySales = _additionalPayeeSecondarySales;
                    projectFinance.additionalPayeeSecondarySalesPercentage = uint8(
                        _additionalPayeeSecondarySalesPercentage
                    );
                    // clear proposed values
                    proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);
                    // emit event for off-chain indexing
                    emit AcceptedArtistAddressesAndSplits(_projectId);
                }
                /**
                 * @notice Updates artist of project `_projectId` to `_artistAddress`.
                 * This is to only be used in the event that the artist address is
                 * compromised or sanctioned.
                 * @param _projectId Project ID.
                 * @param _artistAddress New artist address.
                 */
                function updateProjectArtistAddress(
                    uint256 _projectId,
                    address payable _artistAddress
                ) external {
                    _onlyValidProjectId(_projectId);
                    _onlyAdminACLOrRenouncedArtist(
                        _projectId,
                        this.updateProjectArtistAddress.selector
                    );
                    _onlyNonZeroAddress(_artistAddress);
                    projectIdToFinancials[_projectId].artistAddress = _artistAddress;
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_ADDRESS);
                }
                /**
                 * @notice Toggles paused state of project `_projectId`.
                 * @param _projectId Project ID to be toggled.
                 */
                function toggleProjectIsPaused(uint256 _projectId) external {
                    _onlyArtist(_projectId);
                    projects[_projectId].paused = !projects[_projectId].paused;
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_PAUSED);
                }
                /**
                 * @notice Adds new project `_projectName` by `_artistAddress`.
                 * @param _projectName Project name.
                 * @param _artistAddress Artist's address.
                 * @dev token price now stored on minter
                 */
                function addProject(
                    string memory _projectName,
                    address payable _artistAddress
                ) external {
                    _onlyAdminACL(this.addProject.selector);
                    _onlyNonEmptyString(_projectName);
                    _onlyNonZeroAddress(_artistAddress);
                    require(!newProjectsForbidden, "New projects forbidden");
                    uint256 projectId = _nextProjectId;
                    projectIdToFinancials[projectId].artistAddress = _artistAddress;
                    projects[projectId].name = _projectName;
                    projects[projectId].paused = true;
                    projects[projectId].maxInvocations = ONE_MILLION_UINT24;
                    projects[projectId].projectBaseURI = defaultBaseURI;
                    _nextProjectId = uint248(projectId) + 1;
                    emit ProjectUpdated(projectId, FIELD_PROJECT_CREATED);
                }
                /**
                 * @notice Forever forbids new projects from being added to this contract.
                 */
                function forbidNewProjects() external {
                    _onlyAdminACL(this.forbidNewProjects.selector);
                    require(!newProjectsForbidden, "Already forbidden");
                    _forbidNewProjects();
                }
                /**
                 * @notice Updates name of project `_projectId` to be `_projectName`.
                 * @param _projectId Project ID.
                 * @param _projectName New project name.
                 */
                function updateProjectName(
                    uint256 _projectId,
                    string memory _projectName
                ) external {
                    _onlyUnlocked(_projectId);
                    _onlyArtistOrAdminACL(_projectId, this.updateProjectName.selector);
                    _onlyNonEmptyString(_projectName);
                    projects[_projectId].name = _projectName;
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_NAME);
                }
                /**
                 * @notice Updates artist name for project `_projectId` to be
                 * `_projectArtistName`.
                 * @param _projectId Project ID.
                 * @param _projectArtistName New artist name.
                 */
                function updateProjectArtistName(
                    uint256 _projectId,
                    string memory _projectArtistName
                ) external {
                    _onlyUnlocked(_projectId);
                    _onlyArtistOrAdminACL(
                        _projectId,
                        this.updateProjectArtistName.selector
                    );
                    _onlyNonEmptyString(_projectArtistName);
                    projects[_projectId].artist = _projectArtistName;
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_NAME);
                }
                /**
                 * @notice Updates artist secondary market royalties for project
                 * `_projectId` to be `_secondMarketRoyalty` percent.
                 * This DOES NOT include the secondary market royalty percentages collected
                 * by the issuing platform; it is only the total percentage of royalties
                 * that will be split to artist and additionalSecondaryPayee.
                 * @param _projectId Project ID.
                 * @param _secondMarketRoyalty Percent of secondary sales revenue that will
                 * be split to artist and additionalSecondaryPayee. This must be less than
                 * or equal to ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent.
                 */
                function updateProjectSecondaryMarketRoyaltyPercentage(
                    uint256 _projectId,
                    uint256 _secondMarketRoyalty
                ) external {
                    _onlyArtist(_projectId);
                    require(
                        _secondMarketRoyalty <= ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE,
                        "Over max percent"
                    );
                    projectIdToFinancials[_projectId]
                        .secondaryMarketRoyaltyPercentage = uint8(_secondMarketRoyalty);
                    emit ProjectUpdated(
                        _projectId,
                        FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE
                    );
                }
                /**
                 * @notice Updates description of project `_projectId`.
                 * Only artist may call when unlocked, only admin may call when locked.
                 * Note: The BytecodeStorage library is used to store the description to
                 * reduce initial upload cost, however, even minor edits will require an
                 * expensive, entirely new bytecode storage contract to be deployed instead
                 * of relatively cheap updates to already-warm storage slots. This results
                 * in an increased gas cost for minor edits to the description after the
                 * initial upload, but an overall decrease in gas cost for projects with
                 * less than ~3-5 edits (depending on the length of the description).
                 * @param _projectId Project ID.
                 * @param _projectDescription New project description.
                 */
                function updateProjectDescription(
                    uint256 _projectId,
                    string memory _projectDescription
                ) external {
                    // checks
                    require(
                        _projectUnlocked(_projectId)
                            ? msg.sender == projectIdToFinancials[_projectId].artistAddress
                            : adminACLAllowed(
                                msg.sender,
                                address(this),
                                this.updateProjectDescription.selector
                            ),
                        "Only artist when unlocked, owner when locked"
                    );
                    // effects
                    // store description in contract bytecode, replacing reference address from
                    // the old storage description with the newly created one
                    projects[_projectId].descriptionAddress = _projectDescription
                        .writeToBytecode();
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_DESCRIPTION);
                }
                /**
                 * @notice Updates website of project `_projectId` to be `_projectWebsite`.
                 * @param _projectId Project ID.
                 * @param _projectWebsite New project website.
                 * @dev It is intentionally allowed for this to be set to the empty string.
                 */
                function updateProjectWebsite(
                    uint256 _projectId,
                    string memory _projectWebsite
                ) external {
                    _onlyArtist(_projectId);
                    projects[_projectId].website = _projectWebsite;
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_WEBSITE);
                }
                /**
                 * @notice Updates license for project `_projectId`.
                 * @param _projectId Project ID.
                 * @param _projectLicense New project license.
                 */
                function updateProjectLicense(
                    uint256 _projectId,
                    string memory _projectLicense
                ) external {
                    _onlyUnlocked(_projectId);
                    _onlyArtistOrAdminACL(_projectId, this.updateProjectLicense.selector);
                    _onlyNonEmptyString(_projectLicense);
                    projects[_projectId].license = _projectLicense;
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_LICENSE);
                }
                /**
                 * @notice Updates maximum invocations for project `_projectId` to
                 * `_maxInvocations`. Maximum invocations may only be decreased by the
                 * artist, and must be greater than or equal to current invocations.
                 * New projects are created with maximum invocations of 1 million by
                 * default.
                 * @param _projectId Project ID.
                 * @param _maxInvocations New maximum invocations.
                 */
                function updateProjectMaxInvocations(
                    uint256 _projectId,
                    uint24 _maxInvocations
                ) external {
                    _onlyArtist(_projectId);
                    // CHECKS
                    Project storage project = projects[_projectId];
                    uint256 _invocations = project.invocations;
                    require(
                        (_maxInvocations < project.maxInvocations),
                        "Only maxInvocations decrease"
                    );
                    require(_maxInvocations >= _invocations, "Only gte invocations");
                    // EFFECTS
                    project.maxInvocations = _maxInvocations;
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_MAX_INVOCATIONS);
                    // register completed timestamp if action completed the project
                    if (_maxInvocations == _invocations) {
                        _completeProject(_projectId);
                    }
                }
                /**
                 * @notice Adds a script to project `_projectId`.
                 * @param _projectId Project to be updated.
                 * @param _script Script to be added. Required to be a non-empty string,
                 * but no further validation is performed.
                 */
                function addProjectScript(
                    uint256 _projectId,
                    string memory _script
                ) external {
                    _onlyUnlocked(_projectId);
                    _onlyArtistOrAdminACL(_projectId, this.addProjectScript.selector);
                    _onlyNonEmptyString(_script);
                    Project storage project = projects[_projectId];
                    // store script in contract bytecode
                    project.scriptBytecodeAddresses[project.scriptCount] = _script
                        .writeToBytecode();
                    project.scriptCount = project.scriptCount + 1;
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
                }
                /**
                 * @notice Updates script for project `_projectId` at script ID `_scriptId`.
                 * @param _projectId Project to be updated.
                 * @param _scriptId Script ID to be updated.
                 * @param _script The updated script value. Required to be a non-empty
                 *                string, but no further validation is performed.
                 */
                function updateProjectScript(
                    uint256 _projectId,
                    uint256 _scriptId,
                    string memory _script
                ) external {
                    _onlyUnlocked(_projectId);
                    _onlyArtistOrAdminACL(_projectId, this.updateProjectScript.selector);
                    _onlyNonEmptyString(_script);
                    Project storage project = projects[_projectId];
                    require(_scriptId < project.scriptCount, "scriptId out of range");
                    // store script in contract bytecode, replacing reference address from
                    // the old storage contract with the newly created one
                    project.scriptBytecodeAddresses[_scriptId] = _script.writeToBytecode();
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
                }
                /**
                 * @notice Removes last script from project `_projectId`.
                 * @param _projectId Project to be updated.
                 */
                function removeProjectLastScript(uint256 _projectId) external {
                    _onlyUnlocked(_projectId);
                    _onlyArtistOrAdminACL(
                        _projectId,
                        this.removeProjectLastScript.selector
                    );
                    Project storage project = projects[_projectId];
                    require(project.scriptCount > 0, "No scripts to remove");
                    // delete reference to old storage contract address
                    delete project.scriptBytecodeAddresses[project.scriptCount - 1];
                    unchecked {
                        project.scriptCount = project.scriptCount - 1;
                    }
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
                }
                /**
                 * @notice Updates script type for project `_projectId`.
                 * @param _projectId Project to be updated.
                 * @param _scriptTypeAndVersion Script type and version e.g. "[email protected]",
                 * as bytes32 encoded string.
                 */
                function updateProjectScriptType(
                    uint256 _projectId,
                    bytes32 _scriptTypeAndVersion
                ) external {
                    _onlyUnlocked(_projectId);
                    _onlyArtistOrAdminACL(
                        _projectId,
                        this.updateProjectScriptType.selector
                    );
                    Project storage project = projects[_projectId];
                    // require exactly one @ symbol in _scriptTypeAndVersion
                    require(
                        _scriptTypeAndVersion.containsExactCharacterQty(
                            AT_CHARACTER_CODE,
                            uint8(1)
                        ),
                        "must contain exactly one @"
                    );
                    project.scriptTypeAndVersion = _scriptTypeAndVersion;
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT_TYPE);
                }
                /**
                 * @notice Updates project's aspect ratio.
                 * @param _projectId Project to be updated.
                 * @param _aspectRatio Aspect ratio to be set. Intended to be string in the
                 * format of a decimal, e.g. "1" for square, "1.77777778" for 16:9, etc.,
                 * allowing for a maximum of 10 digits and one (optional) decimal separator.
                 */
                function updateProjectAspectRatio(
                    uint256 _projectId,
                    string memory _aspectRatio
                ) external {
                    _onlyUnlocked(_projectId);
                    _onlyArtistOrAdminACL(
                        _projectId,
                        this.updateProjectAspectRatio.selector
                    );
                    _onlyNonEmptyString(_aspectRatio);
                    // Perform more detailed input validation for aspect ratio.
                    bytes memory aspectRatioBytes = bytes(_aspectRatio);
                    uint256 bytesLength = aspectRatioBytes.length;
                    require(bytesLength <= 11, "Aspect ratio format too long");
                    bool hasSeenDecimalSeparator = false;
                    bool hasSeenNumber = false;
                    for (uint256 i; i < bytesLength; i++) {
                        bytes1 character = aspectRatioBytes[i];
                        // Allow as many #s as desired.
                        if (character >= 0x30 && character <= 0x39) {
                            // 9-0
                            // We need to ensure there is at least 1 `9-0` occurrence.
                            hasSeenNumber = true;
                            continue;
                        }
                        if (character == 0x2E) {
                            // .
                            // Allow no more than 1 `.` occurrence.
                            if (!hasSeenDecimalSeparator) {
                                hasSeenDecimalSeparator = true;
                                continue;
                            }
                        }
                        revert("Improperly formatted aspect ratio");
                    }
                    require(hasSeenNumber, "Aspect ratio has no numbers");
                    projects[_projectId].aspectRatio = _aspectRatio;
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_ASPECT_RATIO);
                }
                /**
                 * @notice Updates base URI for project `_projectId` to `_newBaseURI`.
                 * This is the controlling base URI for all tokens in the project. The
                 * contract-level defaultBaseURI is only used when initializing new
                 * projects.
                 * @param _projectId Project to be updated.
                 * @param _newBaseURI New base URI.
                 */
                function updateProjectBaseURI(
                    uint256 _projectId,
                    string memory _newBaseURI
                ) external {
                    _onlyArtist(_projectId);
                    _onlyNonEmptyString(_newBaseURI);
                    projects[_projectId].projectBaseURI = _newBaseURI;
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_BASE_URI);
                }
                /**
                 * @notice Updates default base URI to `_defaultBaseURI`. The
                 * contract-level defaultBaseURI is only used when initializing new
                 * projects. Token URIs are determined by their project's `projectBaseURI`.
                 * @param _defaultBaseURI New default base URI.
                 */
                function updateDefaultBaseURI(string memory _defaultBaseURI) external {
                    _onlyAdminACL(this.updateDefaultBaseURI.selector);
                    _onlyNonEmptyString(_defaultBaseURI);
                    _updateDefaultBaseURI(_defaultBaseURI);
                }
                /**
                 * @notice Next project ID to be created on this contract.
                 * @return uint256 Next project ID.
                 */
                function nextProjectId() external view returns (uint256) {
                    return _nextProjectId;
                }
                /**
                 * @notice Returns token hash for token ID `_tokenId`. Returns null if hash
                 * has not been set.
                 * @param _tokenId Token ID to be queried.
                 * @return bytes32 Token hash.
                 * @dev token hash is the keccak256 hash of the stored hash seed
                 */
                function tokenIdToHash(uint256 _tokenId) external view returns (bytes32) {
                    bytes12 _hashSeed = _ownersAndHashSeeds[_tokenId].hashSeed;
                    if (_hashSeed == 0) {
                        return 0;
                    }
                    return keccak256(abi.encode(_hashSeed));
                }
                /**
                 * @notice Returns token hash **seed** for token ID `_tokenId`. Returns
                 * null if hash seed has not been set. The hash seed id the bytes12 value
                 * which is hashed to produce the token hash.
                 * @param _tokenId Token ID to be queried.
                 * @return bytes12 Token hash seed.
                 * @dev token hash seed is keccak256 hashed to give the token hash
                 */
                function tokenIdToHashSeed(
                    uint256 _tokenId
                ) external view returns (bytes12) {
                    return _ownersAndHashSeeds[_tokenId].hashSeed;
                }
                /**
                 * @notice View function returning the render provider portion of
                 * primary sales, in percent.
                 * @return uint256 The render provider portion of primary sales,
                 * in percent.
                 */
                function renderProviderPrimarySalesPercentage()
                    external
                    view
                    returns (uint256)
                {
                    return _renderProviderPrimarySalesPercentage;
                }
                /**
                 * @notice View function returning the platform provider portion of
                 * primary sales, in percent.
                 * @return uint256 The platform provider portion of primary sales,
                 * in percent.
                 */
                function platformProviderPrimarySalesPercentage()
                    external
                    view
                    returns (uint256)
                {
                    return _platformProviderPrimarySalesPercentage;
                }
                /**
                 * @notice View function returning Artist's address for project
                 * `_projectId`.
                 * @param _projectId Project ID to be queried.
                 * @return address Artist's address.
                 */
                function projectIdToArtistAddress(
                    uint256 _projectId
                ) external view returns (address payable) {
                    return projectIdToFinancials[_projectId].artistAddress;
                }
                /**
                 * @notice View function returning Artist's secondary market royalty
                 * percentage for project `_projectId`.
                 * This does not include render/platform providers portions of secondary
                 * market royalties.
                 * @param _projectId Project ID to be queried.
                 * @return uint256 Artist's secondary market royalty percentage.
                 */
                function projectIdToSecondaryMarketRoyaltyPercentage(
                    uint256 _projectId
                ) external view returns (uint256) {
                    return
                        projectIdToFinancials[_projectId].secondaryMarketRoyaltyPercentage;
                }
                /**
                 * @notice View function returning Artist's additional payee address for
                 * primary sales, for project `_projectId`.
                 * @param _projectId Project ID to be queried.
                 * @return address Artist's additional payee address for primary sales.
                 */
                function projectIdToAdditionalPayeePrimarySales(
                    uint256 _projectId
                ) external view returns (address payable) {
                    return projectIdToFinancials[_projectId].additionalPayeePrimarySales;
                }
                /**
                 * @notice View function returning Artist's additional payee primary sales
                 * percentage, for project `_projectId`.
                 * @param _projectId Project ID to be queried.
                 * @return uint256 Artist's additional payee primary sales percentage.
                 */
                function projectIdToAdditionalPayeePrimarySalesPercentage(
                    uint256 _projectId
                ) external view returns (uint256) {
                    return
                        projectIdToFinancials[_projectId]
                            .additionalPayeePrimarySalesPercentage;
                }
                /**
                 * @notice View function returning Artist's additional payee address for
                 * secondary sales, for project `_projectId`.
                 * @param _projectId Project ID to be queried.
                 * @return address payable Artist's additional payee address for secondary
                 * sales.
                 */
                function projectIdToAdditionalPayeeSecondarySales(
                    uint256 _projectId
                ) external view returns (address payable) {
                    return projectIdToFinancials[_projectId].additionalPayeeSecondarySales;
                }
                /**
                 * @notice View function returning Artist's additional payee secondary
                 * sales percentage, for project `_projectId`.
                 * @param _projectId Project ID to be queried.
                 * @return uint256 Artist's additional payee secondary sales percentage.
                 */
                function projectIdToAdditionalPayeeSecondarySalesPercentage(
                    uint256 _projectId
                ) external view returns (uint256) {
                    return
                        projectIdToFinancials[_projectId]
                            .additionalPayeeSecondarySalesPercentage;
                }
                /**
                 * @notice Returns project details for project `_projectId`.
                 * @param _projectId Project to be queried.
                 * @return projectName Name of project
                 * @return artist Artist of project
                 * @return description Project description
                 * @return website Project website
                 * @return license Project license
                 * @dev this function was named projectDetails prior to V3 core contract.
                 */
                function projectDetails(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        string memory projectName,
                        string memory artist,
                        string memory description,
                        string memory website,
                        string memory license
                    )
                {
                    Project storage project = projects[_projectId];
                    projectName = project.name;
                    artist = project.artist;
                    address projectDescriptionBytecodeAddress = project.descriptionAddress;
                    if (projectDescriptionBytecodeAddress == address(0)) {
                        description = "";
                    } else {
                        description = _readFromBytecode(projectDescriptionBytecodeAddress);
                    }
                    website = project.website;
                    license = project.license;
                }
                /**
                 * @notice Returns project state data for project `_projectId`.
                 * @param _projectId Project to be queried
                 * @return invocations Current number of invocations
                 * @return maxInvocations Maximum allowed invocations
                 * @return active Boolean representing if project is currently active
                 * @return paused Boolean representing if project is paused
                 * @return completedTimestamp zero if project not complete, otherwise
                 * timestamp of project completion.
                 * @return locked Boolean representing if project is locked
                 * @dev price and currency info are located on minter contracts
                 */
                function projectStateData(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        uint256 invocations,
                        uint256 maxInvocations,
                        bool active,
                        bool paused,
                        uint256 completedTimestamp,
                        bool locked
                    )
                {
                    Project storage project = projects[_projectId];
                    invocations = project.invocations;
                    maxInvocations = project.maxInvocations;
                    active = project.active;
                    paused = project.paused;
                    completedTimestamp = project.completedTimestamp;
                    locked = !_projectUnlocked(_projectId);
                }
                /**
                 * @notice Returns artist payment information for project `_projectId`.
                 * @param _projectId Project to be queried
                 * @return artistAddress Project Artist's address
                 * @return additionalPayeePrimarySales Additional payee address for primary
                 * sales
                 * @return additionalPayeePrimarySalesPercentage Percentage of artist revenue
                 * to be sent to the additional payee address for primary sales
                 * @return additionalPayeeSecondarySales Additional payee address for secondary
                 * sales royalties
                 * @return additionalPayeeSecondarySalesPercentage Percentage of artist revenue
                 * to be sent to the additional payee address for secondary sales royalties
                 * @return secondaryMarketRoyaltyPercentage Royalty percentage to be sent to
                 * combination of artist and additional payee. This does not include the
                 * platform's percentage of secondary sales royalties, which is defined as
                 * the sum of `renderProviderSecondarySalesBPS`
                 * and `platformProviderSecondarySalesBPS`.
                 */
                function projectArtistPaymentInfo(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        address artistAddress,
                        address additionalPayeePrimarySales,
                        uint256 additionalPayeePrimarySalesPercentage,
                        address additionalPayeeSecondarySales,
                        uint256 additionalPayeeSecondarySalesPercentage,
                        uint256 secondaryMarketRoyaltyPercentage
                    )
                {
                    ProjectFinance storage projectFinance = projectIdToFinancials[
                        _projectId
                    ];
                    artistAddress = projectFinance.artistAddress;
                    additionalPayeePrimarySales = projectFinance
                        .additionalPayeePrimarySales;
                    additionalPayeePrimarySalesPercentage = projectFinance
                        .additionalPayeePrimarySalesPercentage;
                    additionalPayeeSecondarySales = projectFinance
                        .additionalPayeeSecondarySales;
                    additionalPayeeSecondarySalesPercentage = projectFinance
                        .additionalPayeeSecondarySalesPercentage;
                    secondaryMarketRoyaltyPercentage = projectFinance
                        .secondaryMarketRoyaltyPercentage;
                }
                /**
                 * @notice Returns script information for project `_projectId`.
                 * @param _projectId Project to be queried.
                 * @return scriptTypeAndVersion Project's script type and version
                 * (e.g. "p5js(atSymbol)1.0.0")
                 * @return aspectRatio Aspect ratio of project (e.g. "1" for square,
                 * "1.77777778" for 16:9, etc.)
                 * @return scriptCount Count of scripts for project
                 */
                function projectScriptDetails(
                    uint256 _projectId
                )
                    external
                    view
                    override(IGenArt721CoreContractV3_Base, IDependencyRegistryCompatibleV0)
                    returns (
                        string memory scriptTypeAndVersion,
                        string memory aspectRatio,
                        uint256 scriptCount
                    )
                {
                    Project storage project = projects[_projectId];
                    scriptTypeAndVersion = project.scriptTypeAndVersion.toString();
                    aspectRatio = project.aspectRatio;
                    scriptCount = project.scriptCount;
                }
                /**
                 * @notice Returns address with bytecode containing project script for
                 * project `_projectId` at script index `_index`.
                 */
                function projectScriptBytecodeAddressByIndex(
                    uint256 _projectId,
                    uint256 _index
                ) external view returns (address) {
                    return projects[_projectId].scriptBytecodeAddresses[_index];
                }
                /**
                 * @notice Returns script for project `_projectId` at script index `_index`.
                 * @param _projectId Project to be queried.
                 * @param _index Index of script to be queried.
                 */
                function projectScriptByIndex(
                    uint256 _projectId,
                    uint256 _index
                ) external view returns (string memory) {
                    Project storage project = projects[_projectId];
                    // If trying to access an out-of-index script, return the empty string.
                    if (_index >= project.scriptCount) {
                        return "";
                    }
                    return _readFromBytecode(project.scriptBytecodeAddresses[_index]);
                }
                /**
                 * @notice Returns base URI for project `_projectId`.
                 * @param _projectId Project to be queried.
                 * @return projectBaseURI Base URI for project
                 */
                function projectURIInfo(
                    uint256 _projectId
                ) external view returns (string memory projectBaseURI) {
                    projectBaseURI = projects[_projectId].projectBaseURI;
                }
                /**
                 * @notice Backwards-compatible (pre-V3) function returning if `_minter` is
                 * minterContract.
                 * @param _minter Address to be queried.
                 * @return bool Boolean representing if `_minter` is minterContract.
                 */
                function isMintWhitelisted(address _minter) external view returns (bool) {
                    return (minterContract == _minter);
                }
                /**
                 * @notice Gets qty of randomizers in history of all randomizers used by
                 * this core contract. If a randomizer is switched away from then back to,
                 * it will show up in the history twice.
                 * @return randomizerHistoryCount Count of randomizers in history
                 */
                function numHistoricalRandomizers() external view returns (uint256) {
                    return _historicalRandomizerAddresses.length;
                }
                /**
                 * @notice Gets address of randomizer at index `_index` in history of all
                 * randomizers used by this core contract. Index is zero-based.
                 * @param _index Historical index of randomizer to be queried.
                 * @return randomizerAddress Address of randomizer at index `_index`.
                 * @dev If a randomizer is switched away from and then switched back to, it
                 * will show up in the history twice.
                 */
                function getHistoricalRandomizerAt(
                    uint256 _index
                ) external view returns (address) {
                    require(
                        _index < _historicalRandomizerAddresses.length,
                        "Index out of bounds"
                    );
                    return _historicalRandomizerAddresses[_index];
                }
                /**
                 * @notice Gets royalty Basis Points (BPS) for token ID `_tokenId`.
                 * This conforms to the IManifold interface designated in the Royalty
                 * Registry's RoyaltyEngineV1.sol contract.
                 * ref: https://github.com/manifoldxyz/royalty-registry-solidity
                 * @param _tokenId Token ID to be queried.
                 * @return recipients Array of royalty payment recipients
                 * @return bps Array of Basis Points (BPS) allocated to each recipient,
                 * aligned by index.
                 * @dev reverts if invalid _tokenId
                 * @dev only returns recipients that have a non-zero BPS allocation
                 */
                function getRoyalties(
                    uint256 _tokenId
                )
                    external
                    view
                    returns (address payable[] memory recipients, uint256[] memory bps)
                {
                    _onlyValidTokenId(_tokenId);
                    // initialize arrays with maximum potential length
                    recipients = new address payable[](4);
                    bps = new uint256[](4);
                    uint256 projectId = tokenIdToProjectId(_tokenId);
                    ProjectFinance storage projectFinance = projectIdToFinancials[
                        projectId
                    ];
                    // load values into memory
                    uint256 royaltyPercentageForArtistAndAdditional = projectFinance
                        .secondaryMarketRoyaltyPercentage;
                    uint256 additionalPayeePercentage = projectFinance
                        .additionalPayeeSecondarySalesPercentage;
                    // calculate BPS = percentage * 100
                    uint256 artistBPS = (ONE_HUNDRED - additionalPayeePercentage) *
                        royaltyPercentageForArtistAndAdditional;
                    uint256 additionalBPS = additionalPayeePercentage *
                        royaltyPercentageForArtistAndAdditional;
                    uint256 renderProviderBPS = renderProviderSecondarySalesBPS;
                    uint256 platformProviderBPS = platformProviderSecondarySalesBPS;
                    // populate arrays
                    uint256 payeeCount;
                    if (artistBPS > 0) {
                        recipients[payeeCount] = projectFinance.artistAddress;
                        bps[payeeCount++] = artistBPS;
                    }
                    if (additionalBPS > 0) {
                        recipients[payeeCount] = projectFinance
                            .additionalPayeeSecondarySales;
                        bps[payeeCount++] = additionalBPS;
                    }
                    if (renderProviderBPS > 0) {
                        recipients[payeeCount] = renderProviderSecondarySalesAddress;
                        bps[payeeCount++] = renderProviderBPS;
                    }
                    if (platformProviderBPS > 0) {
                        recipients[payeeCount] = platformProviderSecondarySalesAddress;
                        bps[payeeCount++] = platformProviderBPS;
                    }
                    // trim arrays if necessary
                    if (4 > payeeCount) {
                        assembly {
                            let decrease := sub(4, payeeCount)
                            mstore(recipients, sub(mload(recipients), decrease))
                            mstore(bps, sub(mload(bps), decrease))
                        }
                    }
                    return (recipients, bps);
                }
                /**
                 * @notice View function that returns appropriate revenue splits between
                 * different render provider, platform provider, Artist, and Artist's
                 * additional primary sales payee given a sale price of `_price` on
                 * project `_projectId`.
                 * This always returns four revenue amounts and four addresses, but if a
                 * revenue is zero for either Artist or additional payee, the corresponding
                 * address returned will also be null (for gas optimization).
                 * Does not account for refund if user overpays for a token (minter should
                 * handle a refund of the difference, if appropriate).
                 * Some minters may have alternative methods of splitting payments, in
                 * which case they should implement their own payment splitting logic.
                 * @param _projectId Project ID to be queried.
                 * @param _price Sale price of token.
                 * @return renderProviderRevenue_ amount of revenue to be sent to the
                 * render provider
                 * @return renderProviderAddress_ address to send render provider revenue to
                 * @return platformProviderRevenue_ amount of revenue to be sent to the
                 * platform provider
                 * @return platformProviderAddress_ address to send platform provider revenue to
                 * @return artistRevenue_ amount of revenue to be sent to Artist
                 * @return artistAddress_ address to send Artist revenue to. Will be null
                 * if no revenue is due to artist (gas optimization).
                 * @return additionalPayeePrimaryRevenue_ amount of revenue to be sent to
                 * additional payee for primary sales
                 * @return additionalPayeePrimaryAddress_ address to send Artist's
                 * additional payee for primary sales revenue to. Will be null if no
                 * revenue is due to additional payee for primary sales (gas optimization).
                 * @dev this always returns four addresses and four revenues, but if the
                 * revenue is zero, the corresponding address will be address(0). It is up
                 * to the contract performing the revenue split to handle this
                 * appropriately.
                 */
                function getPrimaryRevenueSplits(
                    uint256 _projectId,
                    uint256 _price
                )
                    external
                    view
                    returns (
                        uint256 renderProviderRevenue_,
                        address payable renderProviderAddress_,
                        uint256 platformProviderRevenue_,
                        address payable platformProviderAddress_,
                        uint256 artistRevenue_,
                        address payable artistAddress_,
                        uint256 additionalPayeePrimaryRevenue_,
                        address payable additionalPayeePrimaryAddress_
                    )
                {
                    ProjectFinance storage projectFinance = projectIdToFinancials[
                        _projectId
                    ];
                    // calculate revenues – this is a three-way split between the
                    // render provider, the platform provider, and the artist, and
                    // is safe to perform this given that in the case of loss of
                    // precision Solidity will round down.
                    uint256 projectFunds = _price;
                    renderProviderRevenue_ =
                        (_price * uint256(_renderProviderPrimarySalesPercentage)) /
                        ONE_HUNDRED;
                    // renderProviderRevenue_ percentage is always <=100, so guaranteed to never underflow
                    projectFunds -= renderProviderRevenue_;
                    platformProviderRevenue_ =
                        (_price * uint256(_platformProviderPrimarySalesPercentage)) /
                        ONE_HUNDRED;
                    // platformProviderRevenue_ percentage is always <=100, so guaranteed to never underflow
                    projectFunds -= platformProviderRevenue_;
                    additionalPayeePrimaryRevenue_ =
                        (projectFunds *
                            projectFinance.additionalPayeePrimarySalesPercentage) /
                        ONE_HUNDRED;
                    // projectIdToAdditionalPayeePrimarySalesPercentage is always
                    // <=100, so guaranteed to never underflow
                    artistRevenue_ = projectFunds - additionalPayeePrimaryRevenue_;
                    // set addresses from storage
                    renderProviderAddress_ = renderProviderPrimarySalesAddress;
                    platformProviderAddress_ = platformProviderPrimarySalesAddress;
                    if (artistRevenue_ > 0) {
                        artistAddress_ = projectFinance.artistAddress;
                    }
                    if (additionalPayeePrimaryRevenue_ > 0) {
                        additionalPayeePrimaryAddress_ = projectFinance
                            .additionalPayeePrimarySales;
                    }
                }
                /**
                 * @notice Returns external asset dependency for project `_projectId` at index `_index`.
                 * If the dependencyType is ONCHAIN, the `data` field will contain the extrated bytecode data and `cid`
                 * will be an empty string. Conversly, for any other dependencyType, the `data` field will be an empty string
                 * and the `bytecodeAddress` will point to the zero address.
                 */
                function projectExternalAssetDependencyByIndex(
                    uint256 _projectId,
                    uint256 _index
                ) external view returns (ExternalAssetDependencyWithData memory) {
                    ExternalAssetDependency storage _dependency = projects[_projectId]
                        .externalAssetDependencies[_index];
                    address _bytecodeAddress = _dependency.bytecodeAddress;
                    return
                        ExternalAssetDependencyWithData({
                            dependencyType: _dependency.dependencyType,
                            cid: _dependency.cid,
                            bytecodeAddress: _bytecodeAddress,
                            data: (_dependency.dependencyType ==
                                ExternalAssetDependencyType.ONCHAIN)
                                ? _readFromBytecode(_bytecodeAddress)
                                : ""
                        });
                }
                /**
                 * @notice Returns external asset dependency count for project `_projectId` at index `_index`.
                 */
                function projectExternalAssetDependencyCount(
                    uint256 _projectId
                ) external view returns (uint256) {
                    return uint256(projects[_projectId].externalAssetDependencyCount);
                }
                /**
                 * @notice Backwards-compatible (pre-V3) getter returning contract admin
                 * @return address Address of contract admin (same as owner)
                 */
                function admin() external view returns (address) {
                    return owner();
                }
                /**
                 * @notice Gets the project ID for a given `_tokenId`.
                 * @param _tokenId Token ID to be queried.
                 * @return _projectId Project ID for given `_tokenId`.
                 */
                function tokenIdToProjectId(
                    uint256 _tokenId
                ) public pure returns (uint256 _projectId) {
                    return _tokenId / ONE_MILLION;
                }
                /**
                 * @notice Convenience function that returns whether `_sender` is allowed
                 * to call function with selector `_selector` on contract `_contract`, as
                 * determined by this contract's current Admin ACL contract. Expected use
                 * cases include minter contracts checking if caller is allowed to call
                 * admin-gated functions on minter contracts.
                 * @param _sender Address of the sender calling function with selector
                 * `_selector` on contract `_contract`.
                 * @param _contract Address of the contract being called by `_sender`.
                 * @param _selector Function selector of the function being called by
                 * `_sender`.
                 * @return bool Whether `_sender` is allowed to call function with selector
                 * `_selector` on contract `_contract`.
                 * @dev assumes the Admin ACL contract is the owner of this contract, which
                 * is expected to always be true.
                 * @dev adminACLContract is expected to either be null address (if owner
                 * has renounced ownership), or conform to IAdminACLV0 interface. Check for
                 * null address first to avoid revert when admin has renounced ownership.
                 */
                function adminACLAllowed(
                    address _sender,
                    address _contract,
                    bytes4 _selector
                ) public returns (bool) {
                    return
                        owner() != address(0) &&
                        adminACLContract.allowed(_sender, _contract, _selector);
                }
                /**
                 * @notice Returns contract owner. Set to deployer's address by default on
                 * contract deployment.
                 * @return address Address of contract owner.
                 * @dev ref: https://docs.openzeppelin.com/contracts/4.x/api/access#Ownable
                 * @dev owner role was called `admin` prior to V3 core contract
                 */
                function owner()
                    public
                    view
                    override(Ownable, IGenArt721CoreContractV3_Base)
                    returns (address)
                {
                    return Ownable.owner();
                }
                /**
                 * @notice Gets token URI for token ID `_tokenId`.
                 * @param _tokenId Token ID to be queried.
                 * @return string URI of token ID `_tokenId`.
                 * @dev token URIs are the concatenation of the project base URI and the
                 * token ID.
                 */
                function tokenURI(
                    uint256 _tokenId
                ) public view override returns (string memory) {
                    _onlyValidTokenId(_tokenId);
                    string memory _projectBaseURI = projects[tokenIdToProjectId(_tokenId)]
                        .projectBaseURI;
                    return string.concat(_projectBaseURI, toString(_tokenId));
                }
                /**
                 * @dev See {IERC165-supportsInterface}.
                 */
                function supportsInterface(
                    bytes4 interfaceId
                ) public view virtual override returns (bool) {
                    return
                        interfaceId == type(IManifold).interfaceId ||
                        super.supportsInterface(interfaceId);
                }
                /**
                 * @notice Forbids new projects from being created
                 * @dev only performs operation and emits event if contract is not already
                 * forbidding new projects.
                 */
                function _forbidNewProjects() internal {
                    if (!newProjectsForbidden) {
                        newProjectsForbidden = true;
                        emit PlatformUpdated(FIELD_NEW_PROJECTS_FORBIDDEN);
                    }
                }
                /**
                 * @notice Transfers ownership of the contract to a new account (`newOwner`).
                 * Internal function without access restriction.
                 * @param newOwner New owner.
                 * @dev owner role was called `admin` prior to V3 core contract.
                 * @dev Overrides and wraps OpenZeppelin's _transferOwnership function to
                 * also update adminACLContract for improved introspection.
                 */
                function _transferOwnership(address newOwner) internal override {
                    Ownable._transferOwnership(newOwner);
                    adminACLContract = IAdminACLV0(newOwner);
                }
                /**
                 * @notice Updates sales addresses for the platform and render providers to
                 * the input parameters.
                 * @param _renderProviderPrimarySalesAddress Address of new primary sales
                 * payment address.
                 * @param _renderProviderSecondarySalesAddress Address of new secondary sales
                 * payment address.
                 * @param _platformProviderPrimarySalesAddress Address of new primary sales
                 * payment address.
                 * @param _platformProviderSecondarySalesAddress Address of new secondary sales
                 * payment address.
                 * @dev Note that this method does not check that the input address is
                 * not `address(0)`, as it is expected that callers of this method should
                 * perform input validation where applicable.
                 */
                function _updateProviderSalesAddresses(
                    address _renderProviderPrimarySalesAddress,
                    address _renderProviderSecondarySalesAddress,
                    address _platformProviderPrimarySalesAddress,
                    address _platformProviderSecondarySalesAddress
                ) internal {
                    platformProviderPrimarySalesAddress = payable(
                        _platformProviderPrimarySalesAddress
                    );
                    platformProviderSecondarySalesAddress = payable(
                        _platformProviderSecondarySalesAddress
                    );
                    renderProviderPrimarySalesAddress = payable(
                        _renderProviderPrimarySalesAddress
                    );
                    renderProviderSecondarySalesAddress = payable(
                        _renderProviderSecondarySalesAddress
                    );
                    emit PlatformUpdated(FIELD_PROVIDER_SALES_ADDRESSES);
                }
                /**
                 * @notice Updates randomizer address to `_randomizerAddress`.
                 * @param _randomizerAddress New randomizer address.
                 * @dev Note that this method does not check that the input address is
                 * not `address(0)`, as it is expected that callers of this method should
                 * perform input validation where applicable.
                 */
                function _updateRandomizerAddress(address _randomizerAddress) internal {
                    randomizerContract = IRandomizer_V3CoreBase(_randomizerAddress);
                    // populate historical randomizer array
                    _historicalRandomizerAddresses.push(_randomizerAddress);
                    emit PlatformUpdated(FIELD_RANDOMIZER_ADDRESS);
                }
                /**
                 * @notice Updates default base URI to `_defaultBaseURI`.
                 * When new projects are added, their `projectBaseURI` is automatically
                 * initialized to `_defaultBaseURI`.
                 * @param _defaultBaseURI New default base URI.
                 * @dev Note that this method does not check that the input string is not
                 * the empty string, as it is expected that callers of this method should
                 * perform input validation where applicable.
                 */
                function _updateDefaultBaseURI(string memory _defaultBaseURI) internal {
                    defaultBaseURI = _defaultBaseURI;
                    emit PlatformUpdated(FIELD_DEFAULT_BASE_URI);
                }
                /**
                 * @notice Internal function to complete a project.
                 * @param _projectId Project ID to be completed.
                 */
                function _completeProject(uint256 _projectId) internal {
                    projects[_projectId].completedTimestamp = uint64(block.timestamp);
                    emit ProjectUpdated(_projectId, FIELD_PROJECT_COMPLETED);
                }
                /**
                 * @notice Internal function that returns whether a project is unlocked.
                 * Projects automatically lock four weeks after they are completed.
                 * Projects are considered completed when they have been invoked the
                 * maximum number of times.
                 * @param _projectId Project ID to be queried.
                 * @return bool true if project is unlocked, false otherwise.
                 * @dev This also enforces that the `_projectId` passed in is valid.
                 */
                function _projectUnlocked(uint256 _projectId) internal view returns (bool) {
                    _onlyValidProjectId(_projectId);
                    uint256 projectCompletedTimestamp = projects[_projectId]
                        .completedTimestamp;
                    bool projectOpen = projectCompletedTimestamp == 0;
                    return
                        projectOpen ||
                        (block.timestamp - projectCompletedTimestamp <
                            FOUR_WEEKS_IN_SECONDS);
                }
                /**
                 * Helper for calling `BytecodeStorageReader` external library reader method,
                 * added for bytecode size reduction purposes.
                 */
                function _readFromBytecode(
                    address _address
                ) internal view returns (string memory) {
                    return BytecodeStorageReader.readFromBytecode(_address);
                }
                // strings library from OpenZeppelin, modified for no constants
                bytes16 private _HEX_SYMBOLS = "0123456789abcdef";
                uint8 private _ADDRESS_LENGTH = 20;
                /**
                 * @dev Converts a `uint256` to its ASCII `string` decimal representation.
                 */
                function toString(uint256 value) internal pure returns (string memory) {
                    // Inspired by OraclizeAPI's implementation - MIT licence
                    // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
                    if (value == 0) {
                        return "0";
                    }
                    uint256 temp = value;
                    uint256 digits;
                    while (temp != 0) {
                        digits++;
                        temp /= 10;
                    }
                    bytes memory buffer = new bytes(digits);
                    while (value != 0) {
                        digits -= 1;
                        buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                        value /= 10;
                    }
                    return string(buffer);
                }
                /**
                 * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
                 */
                function toHexString(
                    uint256 value,
                    uint256 length
                ) internal view returns (string memory) {
                    bytes memory buffer = new bytes(2 * length + 2);
                    buffer[0] = "0";
                    buffer[1] = "x";
                    for (uint256 i = 2 * length + 1; i > 1; --i) {
                        buffer[i] = _HEX_SYMBOLS[value & 0xf];
                        value >>= 4;
                    }
                    require(value == 0, "hex length insufficient");
                    return string(buffer);
                }
                /**
                 * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
                 */
                function toHexString(address addr) internal view returns (string memory) {
                    return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            interface IAdminACLV0 {
                /**
                 * @notice Token ID `_tokenId` minted to `_to`.
                 * @param previousSuperAdmin The previous superAdmin address.
                 * @param newSuperAdmin The new superAdmin address.
                 * @param genArt721CoreAddressesToUpdate Array of genArt721Core
                 * addresses to update to the new superAdmin, for indexing purposes only.
                 */
                event SuperAdminTransferred(
                    address indexed previousSuperAdmin,
                    address indexed newSuperAdmin,
                    address[] genArt721CoreAddressesToUpdate
                );
                /// Type of the Admin ACL contract, e.g. "AdminACLV0"
                function AdminACLType() external view returns (string memory);
                /// super admin address
                function superAdmin() external view returns (address);
                /**
                 * @notice Calls transferOwnership on other contract from this contract.
                 * This is useful for updating to a new AdminACL contract.
                 * @dev this function should be gated to only superAdmin-like addresses.
                 */
                function transferOwnershipOn(
                    address _contract,
                    address _newAdminACL
                ) external;
                /**
                 * @notice Calls renounceOwnership on other contract from this contract.
                 * @dev this function should be gated to only superAdmin-like addresses.
                 */
                function renounceOwnershipOn(address _contract) external;
                /**
                 * @notice Checks if sender `_sender` is allowed to call function with selector
                 * `_selector` on contract `_contract`.
                 */
                function allowed(
                    address _sender,
                    address _contract,
                    bytes4 _selector
                ) external returns (bool);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.19;
            interface IDependencyRegistryCompatibleV0 {
                /// Dependency registry managed by Art Blocks
                function artblocksDependencyRegistryAddress()
                    external
                    view
                    returns (address);
                /**
                 * @notice Returns script information for project `_projectId`.
                 * @param _projectId Project to be queried.
                 * @return scriptTypeAndVersion Project's script type and version
                 * (e.g. "p5js(atSymbol)1.0.0")
                 * @return aspectRatio Aspect ratio of project (e.g. "1" for square,
                 * "1.77777778" for 16:9, etc.)
                 * @return scriptCount Count of scripts for project
                 */
                function projectScriptDetails(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        string memory scriptTypeAndVersion,
                        string memory aspectRatio,
                        uint256 scriptCount
                    );
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            interface IGenArt721CoreContractExposesHashSeed {
                // function to read the hash-seed for a given tokenId
                function tokenIdToHashSeed(
                    uint256 _tokenId
                ) external view returns (bytes12);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            import "./IAdminACLV0.sol";
            /// use the Royalty Registry's IManifold interface for token royalties
            import "./IManifold.sol";
            /**
             * @title This interface is intended to house interface items that are common
             * across all GenArt721CoreContractV3 flagship and derivative implementations.
             * This interface extends the IManifold royalty interface in order to
             * add support the Royalty Registry by default.
             * @author Art Blocks Inc.
             */
            interface IGenArt721CoreContractV3_Base is IManifold {
                /**
                 * @notice Token ID `_tokenId` minted to `_to`.
                 */
                event Mint(address indexed _to, uint256 indexed _tokenId);
                /**
                 * @notice currentMinter updated to `_currentMinter`.
                 * @dev Implemented starting with V3 core
                 */
                event MinterUpdated(address indexed _currentMinter);
                /**
                 * @notice Platform updated on bytes32-encoded field `_field`.
                 */
                event PlatformUpdated(bytes32 indexed _field);
                /**
                 * @notice Project ID `_projectId` updated on bytes32-encoded field
                 * `_update`.
                 */
                event ProjectUpdated(uint256 indexed _projectId, bytes32 indexed _update);
                event ProposedArtistAddressesAndSplits(
                    uint256 indexed _projectId,
                    address _artistAddress,
                    address _additionalPayeePrimarySales,
                    uint256 _additionalPayeePrimarySalesPercentage,
                    address _additionalPayeeSecondarySales,
                    uint256 _additionalPayeeSecondarySalesPercentage
                );
                event AcceptedArtistAddressesAndSplits(uint256 indexed _projectId);
                // version and type of the core contract
                // coreVersion is a string of the form "0.x.y"
                function coreVersion() external view returns (string memory);
                // coreType is a string of the form "GenArt721CoreV3"
                function coreType() external view returns (string memory);
                // owner (pre-V3 was named admin) of contract
                // this is expected to be an Admin ACL contract for V3
                function owner() external view returns (address);
                // Admin ACL contract for V3, will be at the address owner()
                function adminACLContract() external returns (IAdminACLV0);
                // backwards-compatible (pre-V3) admin - equal to owner()
                function admin() external view returns (address);
                /**
                 * Function determining if _sender is allowed to call function with
                 * selector _selector on contract `_contract`. Intended to be used with
                 * peripheral contracts such as minters, as well as internally by the
                 * core contract itself.
                 */
                function adminACLAllowed(
                    address _sender,
                    address _contract,
                    bytes4 _selector
                ) external returns (bool);
                /// getter function of public variable
                function startingProjectId() external view returns (uint256);
                // getter function of public variable
                function nextProjectId() external view returns (uint256);
                // getter function of public mapping
                function tokenIdToProjectId(
                    uint256 tokenId
                ) external view returns (uint256 projectId);
                // @dev this is not available in V0
                function isMintWhitelisted(address minter) external view returns (bool);
                function projectIdToArtistAddress(
                    uint256 _projectId
                ) external view returns (address payable);
                function projectIdToAdditionalPayeePrimarySales(
                    uint256 _projectId
                ) external view returns (address payable);
                function projectIdToAdditionalPayeePrimarySalesPercentage(
                    uint256 _projectId
                ) external view returns (uint256);
                function projectIdToSecondaryMarketRoyaltyPercentage(
                    uint256 _projectId
                ) external view returns (uint256);
                function projectURIInfo(
                    uint256 _projectId
                ) external view returns (string memory projectBaseURI);
                // @dev new function in V3
                function projectStateData(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        uint256 invocations,
                        uint256 maxInvocations,
                        bool active,
                        bool paused,
                        uint256 completedTimestamp,
                        bool locked
                    );
                function projectDetails(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        string memory projectName,
                        string memory artist,
                        string memory description,
                        string memory website,
                        string memory license
                    );
                function projectScriptDetails(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        string memory scriptTypeAndVersion,
                        string memory aspectRatio,
                        uint256 scriptCount
                    );
                function projectScriptByIndex(
                    uint256 _projectId,
                    uint256 _index
                ) external view returns (string memory);
                function tokenIdToHash(uint256 _tokenId) external view returns (bytes32);
                // function to set a token's hash (must be guarded)
                function setTokenHash_8PT(uint256 _tokenId, bytes32 _hash) external;
                // @dev gas-optimized signature in V3 for `mint`
                function mint_Ecf(
                    address _to,
                    uint256 _projectId,
                    address _by
                ) external returns (uint256 tokenId);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            import "./IAdminACLV0.sol";
            import "./IGenArt721CoreContractV3_Engine.sol";
            /**
             * @title This interface is intended to house interface items that are common
             * across all GenArt721CoreContractV3 Engine Flex and derivative implementations.
             * @author Art Blocks Inc.
             */
            interface IGenArt721CoreContractV3_Engine_Flex is
                IGenArt721CoreContractV3_Engine
            {
                /**
                 * @notice When an external asset dependency is updated or added, this event is emitted.
                 * @param _projectId The project ID of the project that was updated.
                 * @param _index The index of the external asset dependency that was updated.
                 * @param _cid The content ID of the external asset dependency. This is an empty string
                 * if the dependency type is ONCHAIN.
                 * @param _dependencyType The type of the external asset dependency.
                 * @param _externalAssetDependencyCount The number of external asset dependencies.
                 */
                event ExternalAssetDependencyUpdated(
                    uint256 indexed _projectId,
                    uint256 indexed _index,
                    string _cid,
                    ExternalAssetDependencyType _dependencyType,
                    uint24 _externalAssetDependencyCount
                );
                /**
                 * @notice The project id `_projectId` has had an external asset dependency removed at index `_index`.
                 */
                event ExternalAssetDependencyRemoved(
                    uint256 indexed _projectId,
                    uint256 indexed _index
                );
                /**
                 * @notice The preferred gateway for dependency type `_dependencyType` has been updated to `_gatewayAddress`.
                 */
                event GatewayUpdated(
                    ExternalAssetDependencyType indexed _dependencyType,
                    string _gatewayAddress
                );
                /**
                 * @notice The project id `_projectId` has had all external asset dependencies locked.
                 * @dev This is a one-way operation. Once locked, the external asset dependencies cannot be updated.
                 */
                event ProjectExternalAssetDependenciesLocked(uint256 indexed _projectId);
                /**
                 * @notice An external asset dependency type. Can be one of IPFS, ARWEAVE, or ONCHAIN.
                 */
                enum ExternalAssetDependencyType {
                    IPFS,
                    ARWEAVE,
                    ONCHAIN
                }
                /**
                 * @notice An external asset dependency. This is a struct that contains the CID of the dependency,
                 * the type of the dependency, and the address of the bytecode for this dependency.
                 */
                struct ExternalAssetDependency {
                    string cid;
                    ExternalAssetDependencyType dependencyType;
                    address bytecodeAddress;
                }
                /**
                 * @notice An external asset dependency with data. This is a convenience struct that contains the CID of the dependency,
                 * the type of the dependency, the address of the bytecode for this dependency, and the data retrieved from this bytecode address.
                 */
                struct ExternalAssetDependencyWithData {
                    string cid;
                    ExternalAssetDependencyType dependencyType;
                    address bytecodeAddress;
                    string data;
                }
                // preferredIPFSGateway is a url string
                function preferredIPFSGateway() external view returns (string memory);
                // preferredArweaveGateway is a url string
                function preferredArweaveGateway() external view returns (string memory);
                // updates the preferred IPFS gateway
                function updateIPFSGateway(string calldata _gateway) external;
                // updates the preferred Arweave gateway
                function updateArweaveGateway(string calldata _gateway) external;
                // locks the external asset dependencies for a project
                function lockProjectExternalAssetDependencies(uint256 _projectId) external;
                // updates the external asset dependency for a project at a given index
                function updateProjectExternalAssetDependency(
                    uint256 _projectId,
                    uint256 _index,
                    string memory _cidOrData,
                    ExternalAssetDependencyType _dependencyType
                ) external;
                // adds an external asset dependency for a project
                function addProjectExternalAssetDependency(
                    uint256 _projectId,
                    string memory _cidOrData,
                    ExternalAssetDependencyType _dependencyType
                ) external;
                // removes an external asset dependency for a project at a given index
                function removeProjectExternalAssetDependency(
                    uint256 _projectId,
                    uint256 _index
                ) external;
                // getter function for project external asset dependencies
                function projectExternalAssetDependencyByIndex(
                    uint256 _projectId,
                    uint256 _index
                ) external view returns (ExternalAssetDependencyWithData memory);
                // getter function project external asset dependency count
                function projectExternalAssetDependencyCount(
                    uint256 _projectId
                ) external view returns (uint256);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            import "./IAdminACLV0.sol";
            import "./IGenArt721CoreContractV3_Base.sol";
            interface IGenArt721CoreContractV3_Engine is IGenArt721CoreContractV3_Base {
                // @dev new function in V3
                function getPrimaryRevenueSplits(
                    uint256 _projectId,
                    uint256 _price
                )
                    external
                    view
                    returns (
                        uint256 renderProviderRevenue_,
                        address payable renderProviderAddress_,
                        uint256 platformProviderRevenue_,
                        address payable platformProviderAddress_,
                        uint256 artistRevenue_,
                        address payable artistAddress_,
                        uint256 additionalPayeePrimaryRevenue_,
                        address payable additionalPayeePrimaryAddress_
                    );
                // @dev The render provider primary sales payment address
                function renderProviderPrimarySalesAddress()
                    external
                    view
                    returns (address payable);
                // @dev The platform provider primary sales payment address
                function platformProviderPrimarySalesAddress()
                    external
                    view
                    returns (address payable);
                // @dev Percentage of primary sales allocated to the render provider
                function renderProviderPrimarySalesPercentage()
                    external
                    view
                    returns (uint256);
                // @dev Percentage of primary sales allocated to the platform provider
                function platformProviderPrimarySalesPercentage()
                    external
                    view
                    returns (uint256);
                // @dev The render provider secondary sales royalties payment address
                function renderProviderSecondarySalesAddress()
                    external
                    view
                    returns (address payable);
                // @dev The platform provider secondary sales royalties payment address
                function platformProviderSecondarySalesAddress()
                    external
                    view
                    returns (address payable);
                // @dev Basis points of secondary sales allocated to the render provider
                function renderProviderSecondarySalesBPS() external view returns (uint256);
                // @dev Basis points of secondary sales allocated to the platform provider
                function platformProviderSecondarySalesBPS()
                    external
                    view
                    returns (uint256);
            }
            // SPDX-License-Identifier: MIT
            pragma solidity ^0.8.0;
            /// @dev Royalty Registry interface, used to support the Royalty Registry.
            /// @dev Source: https://github.com/manifoldxyz/royalty-registry-solidity/blob/main/contracts/specs/IManifold.sol
            /// @author: manifold.xyz
            /**
             * @dev Royalty interface for creator core classes
             */
            interface IManifold {
                /**
                 * @dev Get royalites of a token.  Returns list of receivers and basisPoints
                 *
                 *  bytes4(keccak256('getRoyalties(uint256)')) == 0xbb3bafd6
                 *
                 *  => 0xbb3bafd6 = 0xbb3bafd6
                 */
                function getRoyalties(
                    uint256 tokenId
                ) external view returns (address payable[] memory, uint256[] memory);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Creatd By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            interface IRandomizer_V3CoreBase {
                /**
                 * @notice This function is intended to be called by a core contract, and
                 * the core contract can be assured that the randomizer will call back to
                 * the calling contract to set the token hash seed for `_tokenId` via
                 * `setTokenHash_8PT`.
                 * @dev This function may revert if hash seed generation is improperly
                 * configured (for example, if in polyptych mode, but no hash seed has been
                 * previously configured).
                 * @dev This function is not specifically gated to any specific caller, but
                 * will only call back to the calling contract, `msg.sender`, to set the
                 * specified token's hash seed.
                 * A third party contract calling this function will not be able to set the
                 * token hash seed on a different core contract.
                 * @param _tokenId The token ID must be assigned a hash.
                 */
                function assignTokenHash(uint256 _tokenId) external;
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            /**
             * @title Art Blocks Script Storage Library
             * @notice Utilize contract bytecode as persistent storage for large chunks of script string data.
             *         This library is intended to have an external deployed copy that is released in the future,
             *         and, as such, has been designed to support both updated V1 (versioned, with purging removed)
             *         reads as well as backwards-compatible reads for both a) the unversioned "V0" storage contracts
             *         which were deployed by the original version of this libary and b) contracts that were deployed
             *         using one of the SSTORE2 implementations referenced below.
             *         For these pre-V1 storage contracts (which themselves did not have any explicit versioning semantics)
             *         backwards-compatible reads are optimistic, and only expected to work for contracts actually
             *         deployed by the original version of this library – and may fail ungracefully if attempted to be
             *         used to read from other contracts.
             *         This library is split into two components, intended to be updated in tandem, and thus included
             *         here in the same source file. One component is an internal library that is intended to be embedded
             *         directly into other contracts and provides all _write_ functionality. The other is a public library
             *         that is intended to be deployed as a standalone contract and provides all _read_ functionality.
             *
             * @author Art Blocks Inc.
             * @author Modified from 0xSequence (https://github.com/0xsequence/sstore2/blob/master/contracts/SSTORE2.sol)
             * @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
             *
             * @dev Compared to the above two rerferenced libraries, this contracts-as-storage implementation makes a few
             *      notably different design decisions:
             *      - uses the `string` data type for input/output on reads, rather than speaking in bytes directly
             *      - stores the "writer" address (library user) in the deployed contract bytes, which is useful for
             *        on-chain introspection and provenance purposes
             *      - stores a very simple versioning string in the deployed contract bytes, which captures the version
             *        of the library that was used to deploy the storage contract and useful for supporting future
             *        compatibility management as this library evolves (e.g. in response to EOF v1 migration plans)
             *      Also, given that much of this library is written in assembly, this library makes use of a slightly
             *      different convention (when compared to the rest of the Art Blocks smart contract repo) around
             *      pre-defining return values in some cases in order to simplify need to directly memory manage these
             *      return values.
             */
            /**
             * @title Art Blocks Script Storage Library (Public, Reads)
             * @author Art Blocks Inc.
             * @notice The public library for reading from storage contracts. This library is intended to be deployed as a
             *         standalone contract, and provides all _read_ functionality.
             */
            library BytecodeStorageReader {
                // Define the set of known valid version strings that may be stored in the deployed storage contract bytecode
                // note: These are all intentionally exactly 32-bytes and are null-terminated. Null-termination is used due
                //       to this being the standard expected formatting in common web3 tooling such as ethers.js. Please see
                //       the following for additional context: https://docs.ethers.org/v5/api/utils/strings/#Bytes32String
                // Used for storage contracts that were deployed by an unknown source
                bytes32 public constant UNKNOWN_VERSION_STRING =
                    "UNKNOWN_VERSION_STRING_________ ";
                // Pre-dates versioning string, so this doesn't actually exist in any deployed contracts,
                // but is useful for backwards-compatible semantics with original version of this library
                bytes32 public constant V0_VERSION_STRING =
                    "BytecodeStorage_V0.0.0_________ ";
                // The first versioned storage contract, deployed by an updated version of this library
                bytes32 public constant V1_VERSION_STRING =
                    "BytecodeStorage_V1.0.0_________ ";
                // The current version of this library.
                bytes32 public constant CURRENT_VERSION = V1_VERSION_STRING;
                //---------------------------------------------------------------------------------------------------------------//
                // Starting Index | Size | Ending Index | Description                                                            //
                //---------------------------------------------------------------------------------------------------------------//
                // 0              | N/A  | 0            |                                                                        //
                // 0              | 1    | 1            | single byte opcode for making the storage contract non-executable      //
                // 1              | 32   | 33           | the 32 byte slot used for storing a basic versioning string            //
                // 33             | 32   | 65           | the 32 bytes for storing the deploying contract's (0-padded) address   //
                //---------------------------------------------------------------------------------------------------------------//
                // Define the offset for where the "meta bytes" end, and the "data bytes" begin. Note that this is a manually
                // calculated value, and must be updated if the above table is changed. It is expected that tests will fail
                // loudly if these values are not updated in-step with eachother.
                uint256 private constant VERSION_OFFSET = 1;
                uint256 private constant ADDRESS_OFFSET = 33;
                uint256 private constant DATA_OFFSET = 65;
                // Define the set of known *historic* offset values for where the "meta bytes" end, and the "data bytes" begin.
                // SSTORE2 deployed storage contracts take the general format of:
                // concat(0x00, data)
                // note: this is true for both variants of the SSTORE2 library
                uint256 private constant SSTORE2_DATA_OFFSET = 1;
                // V0 deployed storage contracts take the general format of:
                // concat(gated-cleanup-logic, deployer-address, data)
                uint256 private constant V0_ADDRESS_OFFSET = 72;
                uint256 private constant V0_DATA_OFFSET = 104;
                // V1 deployed storage contracts take the general format of:
                // concat(invalid opcode, version, deployer-address, data)
                uint256 private constant V1_ADDRESS_OFFSET = ADDRESS_OFFSET;
                uint256 private constant V1_DATA_OFFSET = DATA_OFFSET;
                /*//////////////////////////////////////////////////////////////
                                           READ LOGIC
                //////////////////////////////////////////////////////////////*/
                /**
                 * @notice Read a string from contract bytecode
                 * @param _address address of deployed contract with bytecode stored in the V0 or V1 format
                 * @return data string read from contract bytecode
                 * @dev This function performs input validation that the contract to read is in an expected format
                 */
                function readFromBytecode(
                    address _address
                ) public view returns (string memory data) {
                    uint256 dataOffset = _bytecodeDataOffsetAt(_address);
                    return string(readBytesFromBytecode(_address, dataOffset));
                }
                /**
                 * @notice Read the bytes from contract bytecode that was written to the EVM using SSTORE2
                 * @param _address address of deployed contract with bytecode stored in the SSTORE2 format
                 * @return data bytes read from contract bytecode
                 * @dev This function performs no input validation on the provided contract,
                 *      other than that there is content to read (but not that its a "storage contract")
                 */
                function readBytesFromSSTORE2Bytecode(
                    address _address
                ) public view returns (bytes memory data) {
                    return readBytesFromBytecode(_address, SSTORE2_DATA_OFFSET);
                }
                /**
                 * @notice Read the bytes from contract bytecode, with an explicitly provided starting offset
                 * @param _address address of deployed contract with bytecode stored in the V0 or V1 format
                 * @param _offset offset to read from in contract bytecode, explicitly provided (not calculated)
                 * @return data bytes read from contract bytecode
                 * @dev This function performs no input validation on the provided contract,
                 *      other than that there is content to read (but not that its a "storage contract")
                 */
                function readBytesFromBytecode(
                    address _address,
                    uint256 _offset
                ) public view returns (bytes memory data) {
                    // get the size of the bytecode
                    uint256 bytecodeSize = _bytecodeSizeAt(_address);
                    // handle case where address contains code < _offset
                    if (bytecodeSize < _offset) {
                        revert("ContractAsStorage: Read Error");
                    }
                    // handle case where address contains code >= dataOffset
                    // decrement by dataOffset to account for header info
                    uint256 size;
                    unchecked {
                        size = bytecodeSize - _offset;
                    }
                    assembly {
                        // allocate free memory
                        data := mload(0x40)
                        // update free memory pointer
                        // use and(x, not(0x1f) as cheaper equivalent to sub(x, mod(x, 0x20)).
                        // adding 0x1f to size + logic above ensures the free memory pointer
                        // remains word-aligned, following the Solidity convention.
                        mstore(0x40, add(data, and(add(add(size, 0x20), 0x1f), not(0x1f))))
                        // store length of data in first 32 bytes
                        mstore(data, size)
                        // copy code to memory, excluding the deployer-address
                        extcodecopy(_address, add(data, 0x20), _offset, size)
                    }
                }
                /**
                 * @notice Get address for deployer for given contract bytecode
                 * @param _address address of deployed contract with bytecode stored in the V0 or V1 format
                 * @return writerAddress address read from contract bytecode
                 */
                function getWriterAddressForBytecode(
                    address _address
                ) public view returns (address) {
                    // get the size of the data
                    uint256 bytecodeSize = _bytecodeSizeAt(_address);
                    // the dataOffset for the bytecode
                    uint256 addressOffset = _bytecodeAddressOffsetAt(_address);
                    // handle case where address contains code < addressOffset + 32 (address takes a whole slot)
                    if (bytecodeSize < (addressOffset + 32)) {
                        revert("ContractAsStorage: Read Error");
                    }
                    assembly {
                        // allocate free memory
                        let writerAddress := mload(0x40)
                        // shift free memory pointer by one slot
                        mstore(0x40, add(mload(0x40), 0x20))
                        // copy the 32-byte address of the data contract writer to memory
                        // note: this relies on the assumption noted at the top-level of
                        //       this file that the storage layout for the deployed
                        //       contracts-as-storage contract looks like::
                        //       | invalid opcode | version-string (unless v0) | deployer-address (padded) | data |
                        extcodecopy(
                            _address,
                            writerAddress,
                            addressOffset,
                            0x20 // full 32-bytes, as address is expected to be zero-padded
                        )
                        return(
                            writerAddress,
                            0x20 // return size is entire slot, as it is zero-padded
                        )
                    }
                }
                /**
                 * @notice Get version for given contract bytecode
                 * @param _address address of deployed contract with bytecode stored in the V0 or V1 format
                 * @return version version read from contract bytecode
                 */
                function getLibraryVersionForBytecode(
                    address _address
                ) public view returns (bytes32) {
                    return _bytecodeVersionAt(_address);
                }
                /*//////////////////////////////////////////////////////////////
                                      INTERNAL HELPER LOGIC
                //////////////////////////////////////////////////////////////*/
                /**
                 * @notice Returns the size of the bytecode at address `_address`
                 * @param _address address that may or may not contain bytecode
                 * @return size size of the bytecode code at `_address`
                 */
                function _bytecodeSizeAt(
                    address _address
                ) private view returns (uint256 size) {
                    assembly {
                        size := extcodesize(_address)
                    }
                    if (size == 0) {
                        revert("ContractAsStorage: Read Error");
                    }
                }
                /**
                 * @notice Returns the offset of the data in the bytecode at address `_address`
                 * @param _address address that may or may not contain bytecode
                 * @return dataOffset offset of data in bytecode if a known version, otherwise 0
                 */
                function _bytecodeDataOffsetAt(
                    address _address
                ) private view returns (uint256 dataOffset) {
                    bytes32 version = _bytecodeVersionAt(_address);
                    if (version == V1_VERSION_STRING) {
                        dataOffset = V1_DATA_OFFSET;
                    } else if (version == V0_VERSION_STRING) {
                        dataOffset = V0_DATA_OFFSET;
                    } else {
                        // unknown version, revert
                        revert("ContractAsStorage: Unsupported Version");
                    }
                }
                /**
                 * @notice Returns the offset of the address in the bytecode at address `_address`
                 * @param _address address that may or may not contain bytecode
                 * @return addressOffset offset of address in bytecode if a known version, otherwise 0
                 */
                function _bytecodeAddressOffsetAt(
                    address _address
                ) private view returns (uint256 addressOffset) {
                    bytes32 version = _bytecodeVersionAt(_address);
                    if (version == V1_VERSION_STRING) {
                        addressOffset = V1_ADDRESS_OFFSET;
                    } else if (version == V0_VERSION_STRING) {
                        addressOffset = V0_ADDRESS_OFFSET;
                    } else {
                        // unknown version, revert
                        revert("ContractAsStorage: Unsupported Version");
                    }
                }
                /**
                 * @notice Get version string for given contract bytecode
                 * @param _address address of deployed contract with bytecode stored in the V0 or V1 format
                 * @return version version string read from contract bytecode
                 */
                function _bytecodeVersionAt(
                    address _address
                ) private view returns (bytes32 version) {
                    // get the size of the data
                    uint256 bytecodeSize = _bytecodeSizeAt(_address);
                    // handle case where address contains code < minimum expected version string size,
                    // by returning early with the unknown version string
                    if (bytecodeSize < (VERSION_OFFSET + 32)) {
                        return UNKNOWN_VERSION_STRING;
                    }
                    assembly {
                        // allocate free memory
                        let versionString := mload(0x40)
                        // shift free memory pointer by one slot
                        mstore(0x40, add(mload(0x40), 0x20))
                        // copy the 32-byte version string of the bytecode library to memory
                        // note: this relies on the assumption noted at the top-level of
                        //       this file that the storage layout for the deployed
                        //       contracts-as-storage contract looks like:
                        //       | invalid opcode | version-string (unless v0) | deployer-address (padded) | data |
                        extcodecopy(
                            _address,
                            versionString,
                            VERSION_OFFSET,
                            0x20 // 32-byte version string
                        )
                        // note: must check against literal strings, as Yul does not allow for
                        //       dynamic strings in switch statements.
                        switch mload(versionString)
                        case "BytecodeStorage_V1.0.0_________ " {
                            version := V1_VERSION_STRING
                        }
                        case 0x2060486000396000513314601057fe5b60013614601957fe5b6000357fff0000 {
                            // the v0 variant of this library pre-dates formal versioning w/ version strings,
                            // so we check the first 32 bytes of the execution bytecode itself which
                            // is static and known across all storage contracts deployed with the first version
                            // of this library.
                            version := V0_VERSION_STRING
                        }
                        default {
                            version := UNKNOWN_VERSION_STRING
                        }
                    }
                }
            }
            /**
             * @title Art Blocks Script Storage Library (Internal, Writes)
             * @author Art Blocks Inc.
             * @notice The internal library for writing to storage contracts. This library is intended to be deployed
             *         within library client contracts that use this library to perform _write_ operations on storage.
             */
            library BytecodeStorageWriter {
                /*//////////////////////////////////////////////////////////////
                                       WRITE LOGIC
                //////////////////////////////////////////////////////////////*/
                /**
                 * @notice Write a string to contract bytecode
                 * @param _data string to be written to contract. No input validation is performed on this parameter.
                 * @param address_ address of deployed contract with bytecode stored in the V0 or V1 format
                 */
                function writeToBytecode(
                    string memory _data
                ) internal returns (address address_) {
                    // prefix bytecode with
                    bytes memory creationCode = abi.encodePacked(
                        //---------------------------------------------------------------------------------------------------------------//
                        // Opcode  | Opcode + Arguments  | Description  | Stack View                                                     //
                        //---------------------------------------------------------------------------------------------------------------//
                        // a.) creation code returns all code in the contract except for the first 11 (0B in hex) bytes, as these 11
                        //     bytes are the creation code itself which we do not want to store in the deployed storage contract result
                        //---------------------------------------------------------------------------------------------------------------//
                        // 0x60    |  0x60_0B            | PUSH1 11     | codeOffset                                                     //
                        // 0x59    |  0x59               | MSIZE        | 0 codeOffset                                                   //
                        // 0x81    |  0x81               | DUP2         | codeOffset 0 codeOffset                                        //
                        // 0x38    |  0x38               | CODESIZE     | codeSize codeOffset 0 codeOffset                               //
                        // 0x03    |  0x03               | SUB          | (codeSize - codeOffset) 0 codeOffset                           //
                        // 0x80    |  0x80               | DUP          | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset   //
                        // 0x92    |  0x92               | SWAP3        | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset)   //
                        // 0x59    |  0x59               | MSIZE        | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) //
                        // 0x39    |  0x39               | CODECOPY     | 0 (codeSize - codeOffset)                                      //
                        // 0xF3    |  0xF3               | RETURN       |                                                                //
                        //---------------------------------------------------------------------------------------------------------------//
                        // (11 bytes)
                        hex"60_0B_59_81_38_03_80_92_59_39_F3",
                        //---------------------------------------------------------------------------------------------------------------//
                        // b.) ensure that the deployed storage contract is non-executeable (first opcode is the `invalid` opcode)
                        //---------------------------------------------------------------------------------------------------------------//
                        //---------------------------------------------------------------------------------------------------------------//
                        // 0xFE    |  0xFE               | INVALID      |                                                                //
                        //---------------------------------------------------------------------------------------------------------------//
                        // (1 byte)
                        hex"FE",
                        //---------------------------------------------------------------------------------------------------------------//
                        // c.) store the version string, which is already represented as a 32-byte value
                        //---------------------------------------------------------------------------------------------------------------//
                        // (32 bytes)
                        BytecodeStorageReader.CURRENT_VERSION,
                        //---------------------------------------------------------------------------------------------------------------//
                        // d.) store the deploying-contract's address with 0-padding to fit a 20-byte address into a 32-byte slot
                        //---------------------------------------------------------------------------------------------------------------//
                        // (12 bytes)
                        hex"00_00_00_00_00_00_00_00_00_00_00_00",
                        // (20 bytes)
                        address(this),
                        // uploaded data (stored as bytecode) comes last
                        _data
                    );
                    assembly {
                        // deploy a new contract with the generated creation code.
                        // start 32 bytes into creationCode to avoid copying the byte length.
                        address_ := create(0, add(creationCode, 0x20), mload(creationCode))
                    }
                    // address must be non-zero if contract was deployed successfully
                    require(address_ != address(0), "ContractAsStorage: Write Error");
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            // Inspired by: https://ethereum.stackexchange.com/a/123950/103422
            pragma solidity ^0.8.0;
            /**
             * @dev Operations on bytes32 data type, dealing with conversion to string.
             */
            library Bytes32Strings {
                /**
                 * @dev Intended to convert a `bytes32`-encoded string literal to `string`.
                 * Trims zero padding to arrive at original string literal.
                 */
                function toString(
                    bytes32 source
                ) internal pure returns (string memory result) {
                    uint8 length = 0;
                    while (source[length] != 0 && length < 32) {
                        length++;
                    }
                    assembly {
                        // free memory pointer
                        result := mload(0x40)
                        // update free memory pointer to new "memory end"
                        // (offset is 64-bytes: 32 for length, 32 for data)
                        mstore(0x40, add(result, 0x40))
                        // store length in first 32-byte memory slot
                        mstore(result, length)
                        // write actual data in second 32-byte memory slot
                        mstore(add(result, 0x20), source)
                    }
                }
                /**
                 * @dev Intended to check if a `bytes32`-encoded string contains a given
                 * character with UTF-8 character code `utf8CharCode exactly `targetQty`
                 * times. Does not support searching for multi-byte characters, only
                 * characters with UTF-8 character codes < 0x80.
                 */
                function containsExactCharacterQty(
                    bytes32 source,
                    uint8 utf8CharCode,
                    uint8 targetQty
                ) internal pure returns (bool) {
                    uint8 _occurrences = 0;
                    uint8 i;
                    for (i = 0; i < 32; ) {
                        uint8 _charCode = uint8(source[i]);
                        // if not a null byte, or a multi-byte UTF-8 character, check match
                        if (_charCode != 0 && _charCode < 0x80) {
                            if (_charCode == utf8CharCode) {
                                unchecked {
                                    // no risk of overflow since max 32 iterations < max uin8=255
                                    ++_occurrences;
                                }
                            }
                        }
                        unchecked {
                            // no risk of overflow since max 32 iterations < max uin8=255
                            ++i;
                        }
                    }
                    return _occurrences == targetQty;
                }
            }
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/ERC721.sol)
            pragma solidity ^0.8.0;
            import "@openzeppelin-4.7/contracts/token/ERC721/IERC721.sol";
            import "@openzeppelin-4.7/contracts/token/ERC721/IERC721Receiver.sol";
            import "@openzeppelin-4.7/contracts/token/ERC721/extensions/IERC721Metadata.sol";
            import "@openzeppelin-4.7/contracts/utils/Address.sol";
            import "@openzeppelin-4.7/contracts/utils/Context.sol";
            import "@openzeppelin-4.7/contracts/utils/Strings.sol";
            import "@openzeppelin-4.7/contracts/utils/introspection/ERC165.sol";
            /**
             * @dev Forked version of the OpenZeppelin v4.7.1 ERC721 contract. Utilizes a
             * struct to pack owner and hash seed into a single storage slot.
             * ---------------------
             * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
             * the Metadata extension, but not including the Enumerable extension, which is available separately as
             * {ERC721Enumerable}.
             */
            contract ERC721_PackedHashSeed is Context, ERC165, IERC721, IERC721Metadata {
                using Address for address;
                using Strings for uint256;
                // Token name
                string private _name;
                // Token symbol
                string private _symbol;
                /// struct to pack a token owner and hash seed into same storage slot
                struct OwnerAndHashSeed {
                    // 20 bytes for address of token's owner
                    address owner;
                    // remaining 12 bytes allocated to token hash seed
                    bytes12 hashSeed;
                }
                /// mapping of token ID to OwnerAndHashSeed
                /// @dev visibility internal so inheriting contracts can access
                mapping(uint256 => OwnerAndHashSeed) internal _ownersAndHashSeeds;
                // Mapping owner address to token count
                mapping(address => uint256) private _balances;
                // Mapping from token ID to approved address
                mapping(uint256 => address) private _tokenApprovals;
                // Mapping from owner to operator approvals
                mapping(address => mapping(address => bool)) private _operatorApprovals;
                /**
                 * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
                 */
                constructor(string memory name_, string memory symbol_) {
                    _name = name_;
                    _symbol = symbol_;
                }
                /**
                 * @dev See {IERC165-supportsInterface}.
                 */
                function supportsInterface(
                    bytes4 interfaceId
                ) public view virtual override(ERC165, IERC165) returns (bool) {
                    return
                        interfaceId == type(IERC721).interfaceId ||
                        interfaceId == type(IERC721Metadata).interfaceId ||
                        super.supportsInterface(interfaceId);
                }
                /**
                 * @dev See {IERC721-balanceOf}.
                 */
                function balanceOf(
                    address owner
                ) public view virtual override returns (uint256) {
                    require(
                        owner != address(0),
                        "ERC721: address zero is not a valid owner"
                    );
                    return _balances[owner];
                }
                /**
                 * @dev See {IERC721-ownerOf}.
                 */
                function ownerOf(
                    uint256 tokenId
                ) public view virtual override returns (address) {
                    address owner = _ownersAndHashSeeds[tokenId].owner;
                    require(owner != address(0), "ERC721: invalid token ID");
                    return owner;
                }
                /**
                 * @dev See {IERC721Metadata-name}.
                 */
                function name() public view virtual override returns (string memory) {
                    return _name;
                }
                /**
                 * @dev See {IERC721Metadata-symbol}.
                 */
                function symbol() public view virtual override returns (string memory) {
                    return _symbol;
                }
                /**
                 * @dev See {IERC721Metadata-tokenURI}.
                 */
                function tokenURI(
                    uint256 tokenId
                ) public view virtual override returns (string memory) {
                    _requireMinted(tokenId);
                    string memory baseURI = _baseURI();
                    return
                        bytes(baseURI).length > 0
                            ? string(abi.encodePacked(baseURI, tokenId.toString()))
                            : "";
                }
                /**
                 * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
                 * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
                 * by default, can be overridden in child contracts.
                 */
                function _baseURI() internal view virtual returns (string memory) {
                    return "";
                }
                /**
                 * @dev See {IERC721-approve}.
                 */
                function approve(address to, uint256 tokenId) public virtual override {
                    address owner = ERC721_PackedHashSeed.ownerOf(tokenId);
                    require(to != owner, "ERC721: approval to current owner");
                    require(
                        _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
                        "ERC721: approve caller is not token owner nor approved for all"
                    );
                    _approve(to, tokenId);
                }
                /**
                 * @dev See {IERC721-getApproved}.
                 */
                function getApproved(
                    uint256 tokenId
                ) public view virtual override returns (address) {
                    _requireMinted(tokenId);
                    return _tokenApprovals[tokenId];
                }
                /**
                 * @dev See {IERC721-setApprovalForAll}.
                 */
                function setApprovalForAll(
                    address operator,
                    bool approved
                ) public virtual override {
                    _setApprovalForAll(_msgSender(), operator, approved);
                }
                /**
                 * @dev See {IERC721-isApprovedForAll}.
                 */
                function isApprovedForAll(
                    address owner,
                    address operator
                ) public view virtual override returns (bool) {
                    return _operatorApprovals[owner][operator];
                }
                /**
                 * @dev See {IERC721-transferFrom}.
                 */
                function transferFrom(
                    address from,
                    address to,
                    uint256 tokenId
                ) public virtual override {
                    //solhint-disable-next-line max-line-length
                    require(
                        _isApprovedOrOwner(_msgSender(), tokenId),
                        "ERC721: caller is not token owner nor approved"
                    );
                    _transfer(from, to, tokenId);
                }
                /**
                 * @dev See {IERC721-safeTransferFrom}.
                 */
                function safeTransferFrom(
                    address from,
                    address to,
                    uint256 tokenId
                ) public virtual override {
                    safeTransferFrom(from, to, tokenId, "");
                }
                /**
                 * @dev See {IERC721-safeTransferFrom}.
                 */
                function safeTransferFrom(
                    address from,
                    address to,
                    uint256 tokenId,
                    bytes memory data
                ) public virtual override {
                    require(
                        _isApprovedOrOwner(_msgSender(), tokenId),
                        "ERC721: caller is not token owner nor approved"
                    );
                    _safeTransfer(from, to, tokenId, data);
                }
                /**
                 * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
                 * are aware of the ERC721 protocol to prevent tokens from being forever locked.
                 *
                 * `data` is additional data, it has no specified format and it is sent in call to `to`.
                 *
                 * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
                 * implement alternative mechanisms to perform token transfer, such as signature-based.
                 *
                 * Requirements:
                 *
                 * - `from` cannot be the zero address.
                 * - `to` cannot be the zero address.
                 * - `tokenId` token must exist and be owned by `from`.
                 * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
                 *
                 * Emits a {Transfer} event.
                 */
                function _safeTransfer(
                    address from,
                    address to,
                    uint256 tokenId,
                    bytes memory data
                ) internal virtual {
                    _transfer(from, to, tokenId);
                    require(
                        _checkOnERC721Received(from, to, tokenId, data),
                        "ERC721: transfer to non ERC721Receiver implementer"
                    );
                }
                /**
                 * @dev Returns whether `tokenId` exists.
                 *
                 * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
                 *
                 * Tokens start existing when they are minted (`_mint`),
                 * and stop existing when they are burned (`_burn`).
                 */
                function _exists(uint256 tokenId) internal view virtual returns (bool) {
                    return _ownersAndHashSeeds[tokenId].owner != address(0);
                }
                /**
                 * @dev Returns whether `spender` is allowed to manage `tokenId`.
                 *
                 * Requirements:
                 *
                 * - `tokenId` must exist.
                 */
                function _isApprovedOrOwner(
                    address spender,
                    uint256 tokenId
                ) internal view virtual returns (bool) {
                    address owner = ERC721_PackedHashSeed.ownerOf(tokenId);
                    return (spender == owner ||
                        isApprovedForAll(owner, spender) ||
                        getApproved(tokenId) == spender);
                }
                /**
                 * @dev Safely mints `tokenId` and transfers it to `to`.
                 *
                 * Requirements:
                 *
                 * - `tokenId` must not exist.
                 * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
                 *
                 * Emits a {Transfer} event.
                 */
                function _safeMint(address to, uint256 tokenId) internal virtual {
                    _safeMint(to, tokenId, "");
                }
                /**
                 * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
                 * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
                 */
                function _safeMint(
                    address to,
                    uint256 tokenId,
                    bytes memory data
                ) internal virtual {
                    _mint(to, tokenId);
                    require(
                        _checkOnERC721Received(address(0), to, tokenId, data),
                        "ERC721: transfer to non ERC721Receiver implementer"
                    );
                }
                /**
                 * @dev Mints `tokenId` and transfers it to `to`.
                 *
                 * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
                 *
                 * Requirements:
                 *
                 * - `tokenId` must not exist.
                 * - `to` cannot be the zero address.
                 *
                 * Emits a {Transfer} event.
                 */
                function _mint(address to, uint256 tokenId) internal virtual {
                    require(to != address(0), "ERC721: mint to the zero address");
                    require(!_exists(tokenId), "ERC721: token already minted");
                    _beforeTokenTransfer(address(0), to, tokenId);
                    _balances[to] += 1;
                    _ownersAndHashSeeds[tokenId].owner = to;
                    emit Transfer(address(0), to, tokenId);
                    _afterTokenTransfer(address(0), to, tokenId);
                }
                /**
                 * @dev Destroys `tokenId`.
                 * The approval is cleared when the token is burned.
                 *
                 * Requirements:
                 *
                 * - `tokenId` must exist.
                 *
                 * Emits a {Transfer} event.
                 */
                function _burn(uint256 tokenId) internal virtual {
                    address owner = ERC721_PackedHashSeed.ownerOf(tokenId);
                    _beforeTokenTransfer(owner, address(0), tokenId);
                    // Clear approvals
                    _approve(address(0), tokenId);
                    _balances[owner] -= 1;
                    delete _ownersAndHashSeeds[tokenId].owner;
                    emit Transfer(owner, address(0), tokenId);
                    _afterTokenTransfer(owner, address(0), tokenId);
                }
                /**
                 * @dev Transfers `tokenId` from `from` to `to`.
                 *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
                 *
                 * Requirements:
                 *
                 * - `to` cannot be the zero address.
                 * - `tokenId` token must be owned by `from`.
                 *
                 * Emits a {Transfer} event.
                 */
                function _transfer(
                    address from,
                    address to,
                    uint256 tokenId
                ) internal virtual {
                    require(
                        ERC721_PackedHashSeed.ownerOf(tokenId) == from,
                        "ERC721: transfer from incorrect owner"
                    );
                    require(to != address(0), "ERC721: transfer to the zero address");
                    _beforeTokenTransfer(from, to, tokenId);
                    // Clear approvals from the previous owner
                    _approve(address(0), tokenId);
                    _balances[from] -= 1;
                    _balances[to] += 1;
                    _ownersAndHashSeeds[tokenId].owner = to;
                    emit Transfer(from, to, tokenId);
                    _afterTokenTransfer(from, to, tokenId);
                }
                /**
                 * @dev Approve `to` to operate on `tokenId`
                 *
                 * Emits an {Approval} event.
                 */
                function _approve(address to, uint256 tokenId) internal virtual {
                    _tokenApprovals[tokenId] = to;
                    emit Approval(ERC721_PackedHashSeed.ownerOf(tokenId), to, tokenId);
                }
                /**
                 * @dev Approve `operator` to operate on all of `owner` tokens
                 *
                 * Emits an {ApprovalForAll} event.
                 */
                function _setApprovalForAll(
                    address owner,
                    address operator,
                    bool approved
                ) internal virtual {
                    require(owner != operator, "ERC721: approve to caller");
                    _operatorApprovals[owner][operator] = approved;
                    emit ApprovalForAll(owner, operator, approved);
                }
                /**
                 * @dev Reverts if the `tokenId` has not been minted yet.
                 */
                function _requireMinted(uint256 tokenId) internal view virtual {
                    require(_exists(tokenId), "ERC721: invalid token ID");
                }
                /**
                 * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
                 * The call is not executed if the target address is not a contract.
                 *
                 * @param from address representing the previous owner of the given token ID
                 * @param to target address that will receive the tokens
                 * @param tokenId uint256 ID of the token to be transferred
                 * @param data bytes optional data to send along with the call
                 * @return bool whether the call correctly returned the expected magic value
                 */
                function _checkOnERC721Received(
                    address from,
                    address to,
                    uint256 tokenId,
                    bytes memory data
                ) private returns (bool) {
                    if (to.isContract()) {
                        try
                            IERC721Receiver(to).onERC721Received(
                                _msgSender(),
                                from,
                                tokenId,
                                data
                            )
                        returns (bytes4 retval) {
                            return retval == IERC721Receiver.onERC721Received.selector;
                        } catch (bytes memory reason) {
                            if (reason.length == 0) {
                                revert(
                                    "ERC721: transfer to non ERC721Receiver implementer"
                                );
                            } else {
                                /// @solidity memory-safe-assembly
                                assembly {
                                    revert(add(32, reason), mload(reason))
                                }
                            }
                        }
                    } else {
                        return true;
                    }
                }
                /**
                 * @dev Hook that is called before any token transfer. This includes minting
                 * and burning.
                 *
                 * Calling conditions:
                 *
                 * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
                 * transferred to `to`.
                 * - When `from` is zero, `tokenId` will be minted for `to`.
                 * - When `to` is zero, ``from``'s `tokenId` will be burned.
                 * - `from` and `to` are never both zero.
                 *
                 * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                 */
                function _beforeTokenTransfer(
                    address from,
                    address to,
                    uint256 tokenId
                ) internal virtual {}
                /**
                 * @dev Hook that is called after any transfer of tokens. This includes
                 * minting and burning.
                 *
                 * Calling conditions:
                 *
                 * - when `from` and `to` are both non-zero.
                 * - `from` and `to` are never both zero.
                 *
                 * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
                 */
                function _afterTokenTransfer(
                    address from,
                    address to,
                    uint256 tokenId
                ) internal virtual {}
            }
            

            File 3 of 5: GnosisSafeProxy
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            
            /// @title IProxy - Helper interface to access masterCopy of the Proxy on-chain
            /// @author Richard Meissner - <[email protected]>
            interface IProxy {
                function masterCopy() external view returns (address);
            }
            
            /// @title GnosisSafeProxy - Generic proxy contract allows to execute all transactions applying the code of a master contract.
            /// @author Stefan George - <[email protected]>
            /// @author Richard Meissner - <[email protected]>
            contract GnosisSafeProxy {
                // singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated.
                // To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt`
                address internal singleton;
            
                /// @dev Constructor function sets address of singleton contract.
                /// @param _singleton Singleton address.
                constructor(address _singleton) {
                    require(_singleton != address(0), "Invalid singleton address provided");
                    singleton = _singleton;
                }
            
                /// @dev Fallback function forwards all transactions and returns all received return data.
                fallback() external payable {
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        let _singleton := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)
                        // 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s
                        if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) {
                            mstore(0, _singleton)
                            return(0, 0x20)
                        }
                        calldatacopy(0, 0, calldatasize())
                        let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)
                        returndatacopy(0, 0, returndatasize())
                        if eq(success, 0) {
                            revert(0, returndatasize())
                        }
                        return(0, returndatasize())
                    }
                }
            }
            
            /// @title Proxy Factory - Allows to create new proxy contact and execute a message call to the new proxy within one transaction.
            /// @author Stefan George - <[email protected]>
            contract GnosisSafeProxyFactory {
                event ProxyCreation(GnosisSafeProxy proxy, address singleton);
            
                /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction.
                /// @param singleton Address of singleton contract.
                /// @param data Payload for message call sent to new proxy contract.
                function createProxy(address singleton, bytes memory data) public returns (GnosisSafeProxy proxy) {
                    proxy = new GnosisSafeProxy(singleton);
                    if (data.length > 0)
                        // solhint-disable-next-line no-inline-assembly
                        assembly {
                            if eq(call(gas(), proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) {
                                revert(0, 0)
                            }
                        }
                    emit ProxyCreation(proxy, singleton);
                }
            
                /// @dev Allows to retrieve the runtime code of a deployed Proxy. This can be used to check that the expected Proxy was deployed.
                function proxyRuntimeCode() public pure returns (bytes memory) {
                    return type(GnosisSafeProxy).runtimeCode;
                }
            
                /// @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address.
                function proxyCreationCode() public pure returns (bytes memory) {
                    return type(GnosisSafeProxy).creationCode;
                }
            
                /// @dev Allows to create new proxy contact using CREATE2 but it doesn't run the initializer.
                ///      This method is only meant as an utility to be called from other methods
                /// @param _singleton Address of singleton contract.
                /// @param initializer Payload for message call sent to new proxy contract.
                /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
                function deployProxyWithNonce(
                    address _singleton,
                    bytes memory initializer,
                    uint256 saltNonce
                ) internal returns (GnosisSafeProxy proxy) {
                    // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it
                    bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
                    bytes memory deploymentData = abi.encodePacked(type(GnosisSafeProxy).creationCode, uint256(uint160(_singleton)));
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
                    }
                    require(address(proxy) != address(0), "Create2 call failed");
                }
            
                /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction.
                /// @param _singleton Address of singleton contract.
                /// @param initializer Payload for message call sent to new proxy contract.
                /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
                function createProxyWithNonce(
                    address _singleton,
                    bytes memory initializer,
                    uint256 saltNonce
                ) public returns (GnosisSafeProxy proxy) {
                    proxy = deployProxyWithNonce(_singleton, initializer, saltNonce);
                    if (initializer.length > 0)
                        // solhint-disable-next-line no-inline-assembly
                        assembly {
                            if eq(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0), 0) {
                                revert(0, 0)
                            }
                        }
                    emit ProxyCreation(proxy, _singleton);
                }
            
                /// @dev Allows to create new proxy contact, execute a message call to the new proxy and call a specified callback within one transaction
                /// @param _singleton Address of singleton contract.
                /// @param initializer Payload for message call sent to new proxy contract.
                /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
                /// @param callback Callback that will be invoced after the new proxy contract has been successfully deployed and initialized.
                function createProxyWithCallback(
                    address _singleton,
                    bytes memory initializer,
                    uint256 saltNonce,
                    IProxyCreationCallback callback
                ) public returns (GnosisSafeProxy proxy) {
                    uint256 saltNonceWithCallback = uint256(keccak256(abi.encodePacked(saltNonce, callback)));
                    proxy = createProxyWithNonce(_singleton, initializer, saltNonceWithCallback);
                    if (address(callback) != address(0)) callback.proxyCreated(proxy, _singleton, initializer, saltNonce);
                }
            
                /// @dev Allows to get the address for a new proxy contact created via `createProxyWithNonce`
                ///      This method is only meant for address calculation purpose when you use an initializer that would revert,
                ///      therefore the response is returned with a revert. When calling this method set `from` to the address of the proxy factory.
                /// @param _singleton Address of singleton contract.
                /// @param initializer Payload for message call sent to new proxy contract.
                /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
                function calculateCreateProxyWithNonceAddress(
                    address _singleton,
                    bytes calldata initializer,
                    uint256 saltNonce
                ) external returns (GnosisSafeProxy proxy) {
                    proxy = deployProxyWithNonce(_singleton, initializer, saltNonce);
                    revert(string(abi.encodePacked(proxy)));
                }
            }
            
            interface IProxyCreationCallback {
                function proxyCreated(
                    GnosisSafeProxy proxy,
                    address _singleton,
                    bytes calldata initializer,
                    uint256 saltNonce
                ) external;
            }

            File 4 of 5: MinterFilterV1
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableMap.sol)
            pragma solidity ^0.8.0;
            import "./EnumerableSet.sol";
            /**
             * @dev Library for managing an enumerable variant of Solidity's
             * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
             * type.
             *
             * Maps have the following properties:
             *
             * - Entries are added, removed, and checked for existence in constant time
             * (O(1)).
             * - Entries are enumerated in O(n). No guarantees are made on the ordering.
             *
             * ```
             * contract Example {
             *     // Add the library methods
             *     using EnumerableMap for EnumerableMap.UintToAddressMap;
             *
             *     // Declare a set state variable
             *     EnumerableMap.UintToAddressMap private myMap;
             * }
             * ```
             *
             * As of v3.0.0, only maps of type `uint256 -> address` (`UintToAddressMap`) are
             * supported.
             */
            library EnumerableMap {
                using EnumerableSet for EnumerableSet.Bytes32Set;
                // To implement this library for multiple types with as little code
                // repetition as possible, we write it in terms of a generic Map type with
                // bytes32 keys and values.
                // The Map implementation uses private functions, and user-facing
                // implementations (such as Uint256ToAddressMap) are just wrappers around
                // the underlying Map.
                // This means that we can only create new EnumerableMaps for types that fit
                // in bytes32.
                struct Map {
                    // Storage of keys
                    EnumerableSet.Bytes32Set _keys;
                    mapping(bytes32 => bytes32) _values;
                }
                /**
                 * @dev Adds a key-value pair to a map, or updates the value for an existing
                 * key. O(1).
                 *
                 * Returns true if the key was added to the map, that is if it was not
                 * already present.
                 */
                function _set(
                    Map storage map,
                    bytes32 key,
                    bytes32 value
                ) private returns (bool) {
                    map._values[key] = value;
                    return map._keys.add(key);
                }
                /**
                 * @dev Removes a key-value pair from a map. O(1).
                 *
                 * Returns true if the key was removed from the map, that is if it was present.
                 */
                function _remove(Map storage map, bytes32 key) private returns (bool) {
                    delete map._values[key];
                    return map._keys.remove(key);
                }
                /**
                 * @dev Returns true if the key is in the map. O(1).
                 */
                function _contains(Map storage map, bytes32 key) private view returns (bool) {
                    return map._keys.contains(key);
                }
                /**
                 * @dev Returns the number of key-value pairs in the map. O(1).
                 */
                function _length(Map storage map) private view returns (uint256) {
                    return map._keys.length();
                }
                /**
                 * @dev Returns the key-value pair stored at position `index` in the map. O(1).
                 *
                 * Note that there are no guarantees on the ordering of entries inside the
                 * array, and it may change when more entries are added or removed.
                 *
                 * Requirements:
                 *
                 * - `index` must be strictly less than {length}.
                 */
                function _at(Map storage map, uint256 index) private view returns (bytes32, bytes32) {
                    bytes32 key = map._keys.at(index);
                    return (key, map._values[key]);
                }
                /**
                 * @dev Tries to returns the value associated with `key`.  O(1).
                 * Does not revert if `key` is not in the map.
                 */
                function _tryGet(Map storage map, bytes32 key) private view returns (bool, bytes32) {
                    bytes32 value = map._values[key];
                    if (value == bytes32(0)) {
                        return (_contains(map, key), bytes32(0));
                    } else {
                        return (true, value);
                    }
                }
                /**
                 * @dev Returns the value associated with `key`.  O(1).
                 *
                 * Requirements:
                 *
                 * - `key` must be in the map.
                 */
                function _get(Map storage map, bytes32 key) private view returns (bytes32) {
                    bytes32 value = map._values[key];
                    require(value != 0 || _contains(map, key), "EnumerableMap: nonexistent key");
                    return value;
                }
                /**
                 * @dev Same as {_get}, with a custom error message when `key` is not in the map.
                 *
                 * CAUTION: This function is deprecated because it requires allocating memory for the error
                 * message unnecessarily. For custom revert reasons use {_tryGet}.
                 */
                function _get(
                    Map storage map,
                    bytes32 key,
                    string memory errorMessage
                ) private view returns (bytes32) {
                    bytes32 value = map._values[key];
                    require(value != 0 || _contains(map, key), errorMessage);
                    return value;
                }
                // UintToAddressMap
                struct UintToAddressMap {
                    Map _inner;
                }
                /**
                 * @dev Adds a key-value pair to a map, or updates the value for an existing
                 * key. O(1).
                 *
                 * Returns true if the key was added to the map, that is if it was not
                 * already present.
                 */
                function set(
                    UintToAddressMap storage map,
                    uint256 key,
                    address value
                ) internal returns (bool) {
                    return _set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
                }
                /**
                 * @dev Removes a value from a set. O(1).
                 *
                 * Returns true if the key was removed from the map, that is if it was present.
                 */
                function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) {
                    return _remove(map._inner, bytes32(key));
                }
                /**
                 * @dev Returns true if the key is in the map. O(1).
                 */
                function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) {
                    return _contains(map._inner, bytes32(key));
                }
                /**
                 * @dev Returns the number of elements in the map. O(1).
                 */
                function length(UintToAddressMap storage map) internal view returns (uint256) {
                    return _length(map._inner);
                }
                /**
                 * @dev Returns the element stored at position `index` in the set. O(1).
                 * Note that there are no guarantees on the ordering of values inside the
                 * array, and it may change when more values are added or removed.
                 *
                 * Requirements:
                 *
                 * - `index` must be strictly less than {length}.
                 */
                function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) {
                    (bytes32 key, bytes32 value) = _at(map._inner, index);
                    return (uint256(key), address(uint160(uint256(value))));
                }
                /**
                 * @dev Tries to returns the value associated with `key`.  O(1).
                 * Does not revert if `key` is not in the map.
                 *
                 * _Available since v3.4._
                 */
                function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) {
                    (bool success, bytes32 value) = _tryGet(map._inner, bytes32(key));
                    return (success, address(uint160(uint256(value))));
                }
                /**
                 * @dev Returns the value associated with `key`.  O(1).
                 *
                 * Requirements:
                 *
                 * - `key` must be in the map.
                 */
                function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
                    return address(uint160(uint256(_get(map._inner, bytes32(key)))));
                }
                /**
                 * @dev Same as {get}, with a custom error message when `key` is not in the map.
                 *
                 * CAUTION: This function is deprecated because it requires allocating memory for the error
                 * message unnecessarily. For custom revert reasons use {tryGet}.
                 */
                function get(
                    UintToAddressMap storage map,
                    uint256 key,
                    string memory errorMessage
                ) internal view returns (address) {
                    return address(uint160(uint256(_get(map._inner, bytes32(key), errorMessage))));
                }
            }
            // SPDX-License-Identifier: MIT
            // OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)
            pragma solidity ^0.8.0;
            /**
             * @dev Library for managing
             * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
             * types.
             *
             * Sets have the following properties:
             *
             * - Elements are added, removed, and checked for existence in constant time
             * (O(1)).
             * - Elements are enumerated in O(n). No guarantees are made on the ordering.
             *
             * ```
             * contract Example {
             *     // Add the library methods
             *     using EnumerableSet for EnumerableSet.AddressSet;
             *
             *     // Declare a set state variable
             *     EnumerableSet.AddressSet private mySet;
             * }
             * ```
             *
             * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
             * and `uint256` (`UintSet`) are supported.
             */
            library EnumerableSet {
                // To implement this library for multiple types with as little code
                // repetition as possible, we write it in terms of a generic Set type with
                // bytes32 values.
                // The Set implementation uses private functions, and user-facing
                // implementations (such as AddressSet) are just wrappers around the
                // underlying Set.
                // This means that we can only create new EnumerableSets for types that fit
                // in bytes32.
                struct Set {
                    // Storage of set values
                    bytes32[] _values;
                    // Position of the value in the `values` array, plus 1 because index 0
                    // means a value is not in the set.
                    mapping(bytes32 => uint256) _indexes;
                }
                /**
                 * @dev Add a value to a set. O(1).
                 *
                 * Returns true if the value was added to the set, that is if it was not
                 * already present.
                 */
                function _add(Set storage set, bytes32 value) private returns (bool) {
                    if (!_contains(set, value)) {
                        set._values.push(value);
                        // The value is stored at length-1, but we add 1 to all indexes
                        // and use 0 as a sentinel value
                        set._indexes[value] = set._values.length;
                        return true;
                    } else {
                        return false;
                    }
                }
                /**
                 * @dev Removes a value from a set. O(1).
                 *
                 * Returns true if the value was removed from the set, that is if it was
                 * present.
                 */
                function _remove(Set storage set, bytes32 value) private returns (bool) {
                    // We read and store the value's index to prevent multiple reads from the same storage slot
                    uint256 valueIndex = set._indexes[value];
                    if (valueIndex != 0) {
                        // Equivalent to contains(set, value)
                        // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
                        // the array, and then remove the last element (sometimes called as 'swap and pop').
                        // This modifies the order of the array, as noted in {at}.
                        uint256 toDeleteIndex = valueIndex - 1;
                        uint256 lastIndex = set._values.length - 1;
                        if (lastIndex != toDeleteIndex) {
                            bytes32 lastvalue = set._values[lastIndex];
                            // Move the last value to the index where the value to delete is
                            set._values[toDeleteIndex] = lastvalue;
                            // Update the index for the moved value
                            set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
                        }
                        // Delete the slot where the moved value was stored
                        set._values.pop();
                        // Delete the index for the deleted slot
                        delete set._indexes[value];
                        return true;
                    } else {
                        return false;
                    }
                }
                /**
                 * @dev Returns true if the value is in the set. O(1).
                 */
                function _contains(Set storage set, bytes32 value) private view returns (bool) {
                    return set._indexes[value] != 0;
                }
                /**
                 * @dev Returns the number of values on the set. O(1).
                 */
                function _length(Set storage set) private view returns (uint256) {
                    return set._values.length;
                }
                /**
                 * @dev Returns the value stored at position `index` in the set. O(1).
                 *
                 * Note that there are no guarantees on the ordering of values inside the
                 * array, and it may change when more values are added or removed.
                 *
                 * Requirements:
                 *
                 * - `index` must be strictly less than {length}.
                 */
                function _at(Set storage set, uint256 index) private view returns (bytes32) {
                    return set._values[index];
                }
                /**
                 * @dev Return the entire set in an array
                 *
                 * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
                 * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
                 * this function has an unbounded cost, and using it as part of a state-changing function may render the function
                 * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
                 */
                function _values(Set storage set) private view returns (bytes32[] memory) {
                    return set._values;
                }
                // Bytes32Set
                struct Bytes32Set {
                    Set _inner;
                }
                /**
                 * @dev Add a value to a set. O(1).
                 *
                 * Returns true if the value was added to the set, that is if it was not
                 * already present.
                 */
                function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
                    return _add(set._inner, value);
                }
                /**
                 * @dev Removes a value from a set. O(1).
                 *
                 * Returns true if the value was removed from the set, that is if it was
                 * present.
                 */
                function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
                    return _remove(set._inner, value);
                }
                /**
                 * @dev Returns true if the value is in the set. O(1).
                 */
                function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
                    return _contains(set._inner, value);
                }
                /**
                 * @dev Returns the number of values in the set. O(1).
                 */
                function length(Bytes32Set storage set) internal view returns (uint256) {
                    return _length(set._inner);
                }
                /**
                 * @dev Returns the value stored at position `index` in the set. O(1).
                 *
                 * Note that there are no guarantees on the ordering of values inside the
                 * array, and it may change when more values are added or removed.
                 *
                 * Requirements:
                 *
                 * - `index` must be strictly less than {length}.
                 */
                function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
                    return _at(set._inner, index);
                }
                /**
                 * @dev Return the entire set in an array
                 *
                 * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
                 * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
                 * this function has an unbounded cost, and using it as part of a state-changing function may render the function
                 * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
                 */
                function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
                    return _values(set._inner);
                }
                // AddressSet
                struct AddressSet {
                    Set _inner;
                }
                /**
                 * @dev Add a value to a set. O(1).
                 *
                 * Returns true if the value was added to the set, that is if it was not
                 * already present.
                 */
                function add(AddressSet storage set, address value) internal returns (bool) {
                    return _add(set._inner, bytes32(uint256(uint160(value))));
                }
                /**
                 * @dev Removes a value from a set. O(1).
                 *
                 * Returns true if the value was removed from the set, that is if it was
                 * present.
                 */
                function remove(AddressSet storage set, address value) internal returns (bool) {
                    return _remove(set._inner, bytes32(uint256(uint160(value))));
                }
                /**
                 * @dev Returns true if the value is in the set. O(1).
                 */
                function contains(AddressSet storage set, address value) internal view returns (bool) {
                    return _contains(set._inner, bytes32(uint256(uint160(value))));
                }
                /**
                 * @dev Returns the number of values in the set. O(1).
                 */
                function length(AddressSet storage set) internal view returns (uint256) {
                    return _length(set._inner);
                }
                /**
                 * @dev Returns the value stored at position `index` in the set. O(1).
                 *
                 * Note that there are no guarantees on the ordering of values inside the
                 * array, and it may change when more values are added or removed.
                 *
                 * Requirements:
                 *
                 * - `index` must be strictly less than {length}.
                 */
                function at(AddressSet storage set, uint256 index) internal view returns (address) {
                    return address(uint160(uint256(_at(set._inner, index))));
                }
                /**
                 * @dev Return the entire set in an array
                 *
                 * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
                 * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
                 * this function has an unbounded cost, and using it as part of a state-changing function may render the function
                 * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
                 */
                function values(AddressSet storage set) internal view returns (address[] memory) {
                    bytes32[] memory store = _values(set._inner);
                    address[] memory result;
                    assembly {
                        result := store
                    }
                    return result;
                }
                // UintSet
                struct UintSet {
                    Set _inner;
                }
                /**
                 * @dev Add a value to a set. O(1).
                 *
                 * Returns true if the value was added to the set, that is if it was not
                 * already present.
                 */
                function add(UintSet storage set, uint256 value) internal returns (bool) {
                    return _add(set._inner, bytes32(value));
                }
                /**
                 * @dev Removes a value from a set. O(1).
                 *
                 * Returns true if the value was removed from the set, that is if it was
                 * present.
                 */
                function remove(UintSet storage set, uint256 value) internal returns (bool) {
                    return _remove(set._inner, bytes32(value));
                }
                /**
                 * @dev Returns true if the value is in the set. O(1).
                 */
                function contains(UintSet storage set, uint256 value) internal view returns (bool) {
                    return _contains(set._inner, bytes32(value));
                }
                /**
                 * @dev Returns the number of values on the set. O(1).
                 */
                function length(UintSet storage set) internal view returns (uint256) {
                    return _length(set._inner);
                }
                /**
                 * @dev Returns the value stored at position `index` in the set. O(1).
                 *
                 * Note that there are no guarantees on the ordering of values inside the
                 * array, and it may change when more values are added or removed.
                 *
                 * Requirements:
                 *
                 * - `index` must be strictly less than {length}.
                 */
                function at(UintSet storage set, uint256 index) internal view returns (uint256) {
                    return uint256(_at(set._inner, index));
                }
                /**
                 * @dev Return the entire set in an array
                 *
                 * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
                 * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
                 * this function has an unbounded cost, and using it as part of a state-changing function may render the function
                 * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
                 */
                function values(UintSet storage set) internal view returns (uint256[] memory) {
                    bytes32[] memory store = _values(set._inner);
                    uint256[] memory result;
                    assembly {
                        result := store
                    }
                    return result;
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            interface IAdminACLV0 {
                /**
                 * @notice Token ID `_tokenId` minted to `_to`.
                 * @param previousSuperAdmin The previous superAdmin address.
                 * @param newSuperAdmin The new superAdmin address.
                 * @param genArt721CoreAddressesToUpdate Array of genArt721Core
                 * addresses to update to the new superAdmin, for indexing purposes only.
                 */
                event SuperAdminTransferred(
                    address indexed previousSuperAdmin,
                    address indexed newSuperAdmin,
                    address[] genArt721CoreAddressesToUpdate
                );
                /// Type of the Admin ACL contract, e.g. "AdminACLV0"
                function AdminACLType() external view returns (string memory);
                /// super admin address
                function superAdmin() external view returns (address);
                /**
                 * @notice Calls transferOwnership on other contract from this contract.
                 * This is useful for updating to a new AdminACL contract.
                 * @dev this function should be gated to only superAdmin-like addresses.
                 */
                function transferOwnershipOn(
                    address _contract,
                    address _newAdminACL
                ) external;
                /**
                 * @notice Calls renounceOwnership on other contract from this contract.
                 * @dev this function should be gated to only superAdmin-like addresses.
                 */
                function renounceOwnershipOn(address _contract) external;
                /**
                 * @notice Checks if sender `_sender` is allowed to call function with selector
                 * `_selector` on contract `_contract`.
                 */
                function allowed(
                    address _sender,
                    address _contract,
                    bytes4 _selector
                ) external returns (bool);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            interface IFilteredMinterV0 {
                /**
                 * @notice Price per token in wei updated for project `_projectId` to
                 * `_pricePerTokenInWei`.
                 */
                event PricePerTokenInWeiUpdated(
                    uint256 indexed _projectId,
                    uint256 indexed _pricePerTokenInWei
                );
                /**
                 * @notice Currency updated for project `_projectId` to symbol
                 * `_currencySymbol` and address `_currencyAddress`.
                 */
                event ProjectCurrencyInfoUpdated(
                    uint256 indexed _projectId,
                    address indexed _currencyAddress,
                    string _currencySymbol
                );
                /// togglePurchaseToDisabled updated
                event PurchaseToDisabledUpdated(
                    uint256 indexed _projectId,
                    bool _purchaseToDisabled
                );
                // getter function of public variable
                function minterType() external view returns (string memory);
                function genArt721CoreAddress() external returns (address);
                function minterFilterAddress() external returns (address);
                // Triggers a purchase of a token from the desired project, to the
                // TX-sending address.
                function purchase(
                    uint256 _projectId
                ) external payable returns (uint256 tokenId);
                // Triggers a purchase of a token from the desired project, to the specified
                // receiving address.
                function purchaseTo(
                    address _to,
                    uint256 _projectId
                ) external payable returns (uint256 tokenId);
                // Toggles the ability for `purchaseTo` to be called directly with a
                // specified receiving address that differs from the TX-sending address.
                function togglePurchaseToDisabled(uint256 _projectId) external;
                // Called to make the minter contract aware of the max invocations for a
                // given project.
                function setProjectMaxInvocations(uint256 _projectId) external;
                // Gets if token price is configured, token price in wei, currency symbol,
                // and currency address, assuming this is project's minter.
                // Supersedes any defined core price.
                function getPriceInfo(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        bool isConfigured,
                        uint256 tokenPriceInWei,
                        string memory currencySymbol,
                        address currencyAddress
                    );
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            import "./IAdminACLV0.sol";
            /// use the Royalty Registry's IManifold interface for token royalties
            import "./IManifold.sol";
            /**
             * @title This interface is intended to house interface items that are common
             * across all GenArt721CoreContractV3 flagship and derivative implementations.
             * This interface extends the IManifold royalty interface in order to
             * add support the Royalty Registry by default.
             * @author Art Blocks Inc.
             */
            interface IGenArt721CoreContractV3_Base is IManifold {
                /**
                 * @notice Token ID `_tokenId` minted to `_to`.
                 */
                event Mint(address indexed _to, uint256 indexed _tokenId);
                /**
                 * @notice currentMinter updated to `_currentMinter`.
                 * @dev Implemented starting with V3 core
                 */
                event MinterUpdated(address indexed _currentMinter);
                /**
                 * @notice Platform updated on bytes32-encoded field `_field`.
                 */
                event PlatformUpdated(bytes32 indexed _field);
                /**
                 * @notice Project ID `_projectId` updated on bytes32-encoded field
                 * `_update`.
                 */
                event ProjectUpdated(uint256 indexed _projectId, bytes32 indexed _update);
                event ProposedArtistAddressesAndSplits(
                    uint256 indexed _projectId,
                    address _artistAddress,
                    address _additionalPayeePrimarySales,
                    uint256 _additionalPayeePrimarySalesPercentage,
                    address _additionalPayeeSecondarySales,
                    uint256 _additionalPayeeSecondarySalesPercentage
                );
                event AcceptedArtistAddressesAndSplits(uint256 indexed _projectId);
                // version and type of the core contract
                // coreVersion is a string of the form "0.x.y"
                function coreVersion() external view returns (string memory);
                // coreType is a string of the form "GenArt721CoreV3"
                function coreType() external view returns (string memory);
                // owner (pre-V3 was named admin) of contract
                // this is expected to be an Admin ACL contract for V3
                function owner() external view returns (address);
                // Admin ACL contract for V3, will be at the address owner()
                function adminACLContract() external returns (IAdminACLV0);
                // backwards-compatible (pre-V3) admin - equal to owner()
                function admin() external view returns (address);
                /**
                 * Function determining if _sender is allowed to call function with
                 * selector _selector on contract `_contract`. Intended to be used with
                 * peripheral contracts such as minters, as well as internally by the
                 * core contract itself.
                 */
                function adminACLAllowed(
                    address _sender,
                    address _contract,
                    bytes4 _selector
                ) external returns (bool);
                /// getter function of public variable
                function startingProjectId() external view returns (uint256);
                // getter function of public variable
                function nextProjectId() external view returns (uint256);
                // getter function of public mapping
                function tokenIdToProjectId(
                    uint256 tokenId
                ) external view returns (uint256 projectId);
                // @dev this is not available in V0
                function isMintWhitelisted(address minter) external view returns (bool);
                function projectIdToArtistAddress(
                    uint256 _projectId
                ) external view returns (address payable);
                function projectIdToAdditionalPayeePrimarySales(
                    uint256 _projectId
                ) external view returns (address payable);
                function projectIdToAdditionalPayeePrimarySalesPercentage(
                    uint256 _projectId
                ) external view returns (uint256);
                function projectIdToSecondaryMarketRoyaltyPercentage(
                    uint256 _projectId
                ) external view returns (uint256);
                function projectURIInfo(
                    uint256 _projectId
                ) external view returns (string memory projectBaseURI);
                // @dev new function in V3
                function projectStateData(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        uint256 invocations,
                        uint256 maxInvocations,
                        bool active,
                        bool paused,
                        uint256 completedTimestamp,
                        bool locked
                    );
                function projectDetails(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        string memory projectName,
                        string memory artist,
                        string memory description,
                        string memory website,
                        string memory license
                    );
                function projectScriptDetails(
                    uint256 _projectId
                )
                    external
                    view
                    returns (
                        string memory scriptTypeAndVersion,
                        string memory aspectRatio,
                        uint256 scriptCount
                    );
                function projectScriptByIndex(
                    uint256 _projectId,
                    uint256 _index
                ) external view returns (string memory);
                function tokenIdToHash(uint256 _tokenId) external view returns (bytes32);
                // function to set a token's hash (must be guarded)
                function setTokenHash_8PT(uint256 _tokenId, bytes32 _hash) external;
                // @dev gas-optimized signature in V3 for `mint`
                function mint_Ecf(
                    address _to,
                    uint256 _projectId,
                    address _by
                ) external returns (uint256 tokenId);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            import "./IAdminACLV0.sol";
            import "./IGenArt721CoreContractV3_Base.sol";
            /**
             * @title This interface extends IGenArt721CoreContractV3_Base with functions
             * that are part of the Art Blocks Flagship core contract.
             * @author Art Blocks Inc.
             */
            // This interface extends IGenArt721CoreContractV3_Base with functions that are
            // in part of the Art Blocks Flagship core contract.
            interface IGenArt721CoreContractV3 is IGenArt721CoreContractV3_Base {
                // @dev new function in V3
                function getPrimaryRevenueSplits(
                    uint256 _projectId,
                    uint256 _price
                )
                    external
                    view
                    returns (
                        uint256 artblocksRevenue_,
                        address payable artblocksAddress_,
                        uint256 artistRevenue_,
                        address payable artistAddress_,
                        uint256 additionalPayeePrimaryRevenue_,
                        address payable additionalPayeePrimaryAddress_
                    );
                // @dev Art Blocks primary sales payment address
                function artblocksPrimarySalesAddress()
                    external
                    view
                    returns (address payable);
                /**
                 * @notice Backwards-compatible (pre-V3) function returning Art Blocks
                 * primary sales payment address (now called artblocksPrimarySalesAddress).
                 */
                function artblocksAddress() external view returns (address payable);
                // @dev Percentage of primary sales allocated to Art Blocks
                function artblocksPrimarySalesPercentage() external view returns (uint256);
                /**
                 * @notice Backwards-compatible (pre-V3) function returning Art Blocks
                 * primary sales percentage (now called artblocksPrimarySalesPercentage).
                 */
                function artblocksPercentage() external view returns (uint256);
                // @dev Art Blocks secondary sales royalties payment address
                function artblocksSecondarySalesAddress()
                    external
                    view
                    returns (address payable);
                // @dev Basis points of secondary sales allocated to Art Blocks
                function artblocksSecondarySalesBPS() external view returns (uint256);
                /**
                 * @notice Backwards-compatible (pre-V3) function  that gets artist +
                 * artist's additional payee royalty data for token ID `_tokenId`.
                 * WARNING: Does not include Art Blocks portion of royalties.
                 */
                function getRoyaltyData(
                    uint256 _tokenId
                )
                    external
                    view
                    returns (
                        address artistAddress,
                        address additionalPayee,
                        uint256 additionalPayeePercentage,
                        uint256 royaltyFeeByID
                    );
            }
            // SPDX-License-Identifier: MIT
            pragma solidity ^0.8.0;
            /// @dev Royalty Registry interface, used to support the Royalty Registry.
            /// @dev Source: https://github.com/manifoldxyz/royalty-registry-solidity/blob/main/contracts/specs/IManifold.sol
            /// @author: manifold.xyz
            /**
             * @dev Royalty interface for creator core classes
             */
            interface IManifold {
                /**
                 * @dev Get royalites of a token.  Returns list of receivers and basisPoints
                 *
                 *  bytes4(keccak256('getRoyalties(uint256)')) == 0xbb3bafd6
                 *
                 *  => 0xbb3bafd6 = 0xbb3bafd6
                 */
                function getRoyalties(
                    uint256 tokenId
                ) external view returns (address payable[] memory, uint256[] memory);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            pragma solidity ^0.8.0;
            interface IMinterFilterV0 {
                /**
                 * @notice Emitted when contract is deployed to notify indexing services
                 * of the new contract deployment.
                 */
                event Deployed();
                /**
                 * @notice Approved minter `_minterAddress`.
                 */
                event MinterApproved(address indexed _minterAddress, string _minterType);
                /**
                 * @notice Revoked approval for minter `_minterAddress`
                 */
                event MinterRevoked(address indexed _minterAddress);
                /**
                 * @notice Minter `_minterAddress` of type `_minterType`
                 * registered for project `_projectId`.
                 */
                event ProjectMinterRegistered(
                    uint256 indexed _projectId,
                    address indexed _minterAddress,
                    string _minterType
                );
                /**
                 * @notice Any active minter removed for project `_projectId`.
                 */
                event ProjectMinterRemoved(uint256 indexed _projectId);
                function genArt721CoreAddress() external returns (address);
                function setMinterForProject(uint256, address) external;
                function removeMinterForProject(uint256) external;
                function mint(
                    address _to,
                    uint256 _projectId,
                    address sender
                ) external returns (uint256);
                function getMinterForProject(uint256) external view returns (address);
                function projectHasMinter(uint256) external view returns (bool);
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            // Inspired by: https://ethereum.stackexchange.com/a/123950/103422
            pragma solidity ^0.8.0;
            /**
             * @dev Operations on bytes32 data type, dealing with conversion to string.
             */
            library Bytes32Strings {
                /**
                 * @dev Intended to convert a `bytes32`-encoded string literal to `string`.
                 * Trims zero padding to arrive at original string literal.
                 */
                function toString(
                    bytes32 source
                ) internal pure returns (string memory result) {
                    uint8 length = 0;
                    while (source[length] != 0 && length < 32) {
                        length++;
                    }
                    assembly {
                        // free memory pointer
                        result := mload(0x40)
                        // update free memory pointer to new "memory end"
                        // (offset is 64-bytes: 32 for length, 32 for data)
                        mstore(0x40, add(result, 0x40))
                        // store length in first 32-byte memory slot
                        mstore(result, length)
                        // write actual data in second 32-byte memory slot
                        mstore(add(result, 0x20), source)
                    }
                }
                /**
                 * @dev Intended to check if a `bytes32`-encoded string contains a given
                 * character with UTF-8 character code `utf8CharCode exactly `targetQty`
                 * times. Does not support searching for multi-byte characters, only
                 * characters with UTF-8 character codes < 0x80.
                 */
                function containsExactCharacterQty(
                    bytes32 source,
                    uint8 utf8CharCode,
                    uint8 targetQty
                ) internal pure returns (bool) {
                    uint8 _occurrences = 0;
                    uint8 i;
                    for (i = 0; i < 32; ) {
                        uint8 _charCode = uint8(source[i]);
                        // if not a null byte, or a multi-byte UTF-8 character, check match
                        if (_charCode != 0 && _charCode < 0x80) {
                            if (_charCode == utf8CharCode) {
                                unchecked {
                                    // no risk of overflow since max 32 iterations < max uin8=255
                                    ++_occurrences;
                                }
                            }
                        }
                        unchecked {
                            // no risk of overflow since max 32 iterations < max uin8=255
                            ++i;
                        }
                    }
                    return _occurrences == targetQty;
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            // Created By: Art Blocks Inc.
            import "../../interfaces/v0.8.x/IMinterFilterV0.sol";
            import "../../interfaces/v0.8.x/IFilteredMinterV0.sol";
            import "../../interfaces/v0.8.x/IAdminACLV0.sol";
            import "../../interfaces/v0.8.x/IGenArt721CoreContractV3.sol";
            import "../../libs/v0.8.x/Bytes32Strings.sol";
            import "@openzeppelin-4.5/contracts/utils/structs/EnumerableMap.sol";
            pragma solidity 0.8.19;
            /**
             * @title Minter filter contract that allows filtered minters to be set
             * on a per-project basis.
             * This is designed to be used with IGenArt721CoreContractV3 contracts.
             * @author Art Blocks Inc.
             * @notice Privileged Roles and Ownership:
             * This contract is designed to be managed, with limited powers.
             * Privileged roles and abilities are controlled by the core contract's Admin
             * ACL contract and a project's artist. Both of these roles hold extensive
             * power and can modify a project's current minter.
             * Care must be taken to ensure that the admin ACL contract and artist
             * addresses are secure behind a multi-sig or other access control mechanism.
             * ----------------------------------------------------------------------------
             * The following functions are restricted to the core contract's Admin ACL
             * contract:
             * - addApprovedMinter
             * - removeApprovedMinter
             * - removeMintersForProjects
             * ----------------------------------------------------------------------------
             * The following functions are restricted to the core contract's Admin ACL
             * contract or a project's artist:
             * - setMinterForProject
             * - removeMinterForProject
             * ----------------------------------------------------------------------------
             * Additional admin and artist privileged roles may be described on minters
             */
            contract MinterFilterV1 is IMinterFilterV0 {
                // add Enumerable Map methods
                using EnumerableMap for EnumerableMap.UintToAddressMap;
                // add Bytes32Strings methods
                using Bytes32Strings for bytes32;
                /// version & type of this core contract
                bytes32 constant MINTER_FILTER_VERSION = "v1.0.1";
                function minterFilterVersion() external pure returns (string memory) {
                    return MINTER_FILTER_VERSION.toString();
                }
                bytes32 constant MINTER_FILTER_TYPE = "MinterFilterV1";
                function minterFilterType() external pure returns (string memory) {
                    return MINTER_FILTER_TYPE.toString();
                }
                /// Core contract address this minter interacts with
                address public immutable genArt721CoreAddress;
                /// This contract integrates with the IV3 core contract
                IGenArt721CoreContractV3 private immutable genArtCoreContract;
                /// projectId => minter address
                EnumerableMap.UintToAddressMap private minterForProject;
                /// minter address => qty projects currently using minter
                mapping(address => uint256) public numProjectsUsingMinter;
                /// minter address => is an approved minter?
                mapping(address => bool) public isApprovedMinter;
                function _onlyNonZeroAddress(address _address) internal pure {
                    require(_address != address(0), "Must input non-zero address");
                }
                // function to restrict access to only AdminACL allowed calls
                // @dev defers which ACL contract is used to the core contract
                function _onlyCoreAdminACL(bytes4 _selector) internal {
                    require(_coreAdminACLAllowed(_selector), "Only Core AdminACL allowed");
                }
                // function to restrict access to only the artist of `_projectId`, or
                // AdminACL allowed calls
                // @dev defers which ACL contract is used to the core contract
                function _onlyCoreAdminACLOrArtist(
                    uint256 _projectId,
                    bytes4 _selector
                ) internal {
                    require(
                        msg.sender ==
                            genArtCoreContract.projectIdToArtistAddress(_projectId) ||
                            _coreAdminACLAllowed(_selector),
                        "Only Core AdminACL or Artist"
                    );
                }
                function _onlyValidProjectId(uint256 _projectId) internal view {
                    require(
                        _projectId < genArtCoreContract.nextProjectId(),
                        "Only existing projects"
                    );
                }
                function _usingApprovedMinter(address _minterAddress) internal view {
                    require(
                        isApprovedMinter[_minterAddress],
                        "Only approved minters are allowed"
                    );
                }
                /**
                 * @notice Initializes contract to be a Minter for `_genArt721Address`.
                 * @param _genArt721Address Art Blocks core contract address
                 * this contract will be a minter for. Can never be updated.
                 */
                constructor(address _genArt721Address) {
                    _onlyNonZeroAddress(_genArt721Address);
                    genArt721CoreAddress = _genArt721Address;
                    genArtCoreContract = IGenArt721CoreContractV3(_genArt721Address);
                    emit Deployed();
                }
                /**
                 * @notice Internal function that determines if msg.sender is allowed to
                 * call a function on this contract. Defers to core contract's
                 * adminACLAllowed function.
                 */
                function _coreAdminACLAllowed(bytes4 _selector) internal returns (bool) {
                    return
                        genArtCoreContract.adminACLAllowed(
                            msg.sender,
                            address(this),
                            _selector
                        );
                }
                /**
                 * @notice Approves minter `_minterAddress`.
                 * @param _minterAddress Minter to be added as an approved minter.
                 */
                function addApprovedMinter(address _minterAddress) external {
                    _onlyCoreAdminACL(this.addApprovedMinter.selector);
                    _onlyNonZeroAddress(_minterAddress);
                    isApprovedMinter[_minterAddress] = true;
                    emit MinterApproved(
                        _minterAddress,
                        IFilteredMinterV0(_minterAddress).minterType()
                    );
                }
                /**
                 * @notice Removes previously approved minter `_minterAddress`.
                 * @param _minterAddress Minter to remove.
                 */
                function removeApprovedMinter(address _minterAddress) external {
                    _onlyCoreAdminACL(this.removeApprovedMinter.selector);
                    require(isApprovedMinter[_minterAddress], "Only approved minters");
                    require(
                        numProjectsUsingMinter[_minterAddress] == 0,
                        "Only unused minters"
                    );
                    isApprovedMinter[_minterAddress] = false;
                    emit MinterRevoked(_minterAddress);
                }
                /**
                 * @notice Sets minter for project `_projectId` to minter
                 * `_minterAddress`.
                 * @param _projectId Project ID to set minter for.
                 * @param _minterAddress Minter to be the project's minter.
                 */
                function setMinterForProject(
                    uint256 _projectId,
                    address _minterAddress
                ) external {
                    _onlyCoreAdminACLOrArtist(
                        _projectId,
                        this.setMinterForProject.selector
                    );
                    _usingApprovedMinter(_minterAddress);
                    _onlyValidProjectId(_projectId);
                    // decrement number of projects using a previous minter
                    (bool hasPreviousMinter, address previousMinter) = minterForProject
                        .tryGet(_projectId);
                    if (hasPreviousMinter) {
                        numProjectsUsingMinter[previousMinter]--;
                    }
                    // add new minter
                    numProjectsUsingMinter[_minterAddress]++;
                    minterForProject.set(_projectId, _minterAddress);
                    emit ProjectMinterRegistered(
                        _projectId,
                        _minterAddress,
                        IFilteredMinterV0(_minterAddress).minterType()
                    );
                }
                /**
                 * @notice Updates project `_projectId` to have no configured minter.
                 * @param _projectId Project ID to remove minter.
                 * @dev requires project to have an assigned minter
                 */
                function removeMinterForProject(uint256 _projectId) external {
                    _onlyCoreAdminACLOrArtist(
                        _projectId,
                        this.removeMinterForProject.selector
                    );
                    _removeMinterForProject(_projectId);
                }
                /**
                 * @notice Updates an array of project IDs to have no configured minter.
                 * @param _projectIds Array of project IDs to remove minters for.
                 * @dev requires all project IDs to have an assigned minter
                 * @dev caution with respect to single tx gas limits
                 */
                function removeMintersForProjects(uint256[] calldata _projectIds) external {
                    _onlyCoreAdminACL(this.removeMintersForProjects.selector);
                    uint256 numProjects = _projectIds.length;
                    for (uint256 i; i < numProjects; i++) {
                        _removeMinterForProject(_projectIds[i]);
                    }
                }
                /**
                 * @notice Updates project `_projectId` to have no configured minter
                 * (reverts tx if project does not have an assigned minter).
                 * @param _projectId Project ID to remove minter.
                 */
                function _removeMinterForProject(uint256 _projectId) private {
                    // remove minter for project and emit
                    // `minterForProject.get()` reverts tx if no minter set for project
                    numProjectsUsingMinter[minterForProject.get(_projectId)]--;
                    minterForProject.remove(_projectId);
                    emit ProjectMinterRemoved(_projectId);
                }
                /**
                 * @notice Mint a token from project `_projectId` to `_to`, originally
                 * purchased by `sender`.
                 * @param _to The new token's owner.
                 * @param _projectId Project ID to mint a new token on.
                 * @param sender Address purchasing a new token.
                 * @return _tokenId Token ID of minted token
                 * @dev reverts w/nonexistent key error when project has no assigned minter
                 */
                function mint(
                    address _to,
                    uint256 _projectId,
                    address sender
                ) external returns (uint256 _tokenId) {
                    // CHECKS
                    // minter is the project's minter
                    require(
                        msg.sender == minterForProject.get(_projectId),
                        "Only assigned minter"
                    );
                    // EFFECTS
                    uint256 tokenId = genArtCoreContract.mint_Ecf(_to, _projectId, sender);
                    return tokenId;
                }
                /**
                 * @notice Gets the assigned minter for project `_projectId`.
                 * @param _projectId Project ID to query.
                 * @return address Minter address assigned to project `_projectId`
                 * @dev requires project to have an assigned minter
                 */
                function getMinterForProject(
                    uint256 _projectId
                ) external view returns (address) {
                    _onlyValidProjectId(_projectId);
                    (bool _hasMinter, address _currentMinter) = minterForProject.tryGet(
                        _projectId
                    );
                    require(_hasMinter, "No minter assigned");
                    return _currentMinter;
                }
                /**
                 * @notice Queries if project `_projectId` has an assigned minter.
                 * @param _projectId Project ID to query.
                 * @return bool true if project has an assigned minter, else false
                 */
                function projectHasMinter(uint256 _projectId) external view returns (bool) {
                    _onlyValidProjectId(_projectId);
                    (bool _hasMinter, ) = minterForProject.tryGet(_projectId);
                    return _hasMinter;
                }
                /**
                 * @notice Gets quantity of projects that have assigned minters.
                 * @return uint256 quantity of projects that have assigned minters
                 */
                function getNumProjectsWithMinters() external view returns (uint256) {
                    return minterForProject.length();
                }
                /**
                 * @notice Get project ID and minter address at index `_index` of
                 * enumerable map.
                 * @param _index enumerable map index to query.
                 * @return projectId project ID at index `_index`
                 * @return minterAddress minter address for project at index `_index`
                 * @return minterType minter type of minter at minterAddress
                 * @dev index must be < quantity of projects that have assigned minters
                 */
                function getProjectAndMinterInfoAt(
                    uint256 _index
                )
                    external
                    view
                    returns (
                        uint256 projectId,
                        address minterAddress,
                        string memory minterType
                    )
                {
                    (projectId, minterAddress) = minterForProject.at(_index);
                    minterType = IFilteredMinterV0(minterAddress).minterType();
                    return (projectId, minterAddress, minterType);
                }
            }
            

            File 5 of 5: GnosisSafe
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            import "./base/ModuleManager.sol";
            import "./base/OwnerManager.sol";
            import "./base/FallbackManager.sol";
            import "./base/GuardManager.sol";
            import "./common/EtherPaymentFallback.sol";
            import "./common/Singleton.sol";
            import "./common/SignatureDecoder.sol";
            import "./common/SecuredTokenTransfer.sol";
            import "./common/StorageAccessible.sol";
            import "./interfaces/ISignatureValidator.sol";
            import "./external/GnosisSafeMath.sol";
            /// @title Gnosis Safe - A multisignature wallet with support for confirmations using signed messages based on ERC191.
            /// @author Stefan George - <[email protected]>
            /// @author Richard Meissner - <[email protected]>
            contract GnosisSafe is
                EtherPaymentFallback,
                Singleton,
                ModuleManager,
                OwnerManager,
                SignatureDecoder,
                SecuredTokenTransfer,
                ISignatureValidatorConstants,
                FallbackManager,
                StorageAccessible,
                GuardManager
            {
                using GnosisSafeMath for uint256;
                string public constant VERSION = "1.3.0";
                // keccak256(
                //     "EIP712Domain(uint256 chainId,address verifyingContract)"
                // );
                bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218;
                // keccak256(
                //     "SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)"
                // );
                bytes32 private constant SAFE_TX_TYPEHASH = 0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8;
                event SafeSetup(address indexed initiator, address[] owners, uint256 threshold, address initializer, address fallbackHandler);
                event ApproveHash(bytes32 indexed approvedHash, address indexed owner);
                event SignMsg(bytes32 indexed msgHash);
                event ExecutionFailure(bytes32 txHash, uint256 payment);
                event ExecutionSuccess(bytes32 txHash, uint256 payment);
                uint256 public nonce;
                bytes32 private _deprecatedDomainSeparator;
                // Mapping to keep track of all message hashes that have been approve by ALL REQUIRED owners
                mapping(bytes32 => uint256) public signedMessages;
                // Mapping to keep track of all hashes (message or transaction) that have been approve by ANY owners
                mapping(address => mapping(bytes32 => uint256)) public approvedHashes;
                // This constructor ensures that this contract can only be used as a master copy for Proxy contracts
                constructor() {
                    // By setting the threshold it is not possible to call setup anymore,
                    // so we create a Safe with 0 owners and threshold 1.
                    // This is an unusable Safe, perfect for the singleton
                    threshold = 1;
                }
                /// @dev Setup function sets initial storage of contract.
                /// @param _owners List of Safe owners.
                /// @param _threshold Number of required confirmations for a Safe transaction.
                /// @param to Contract address for optional delegate call.
                /// @param data Data payload for optional delegate call.
                /// @param fallbackHandler Handler for fallback calls to this contract
                /// @param paymentToken Token that should be used for the payment (0 is ETH)
                /// @param payment Value that should be paid
                /// @param paymentReceiver Adddress that should receive the payment (or 0 if tx.origin)
                function setup(
                    address[] calldata _owners,
                    uint256 _threshold,
                    address to,
                    bytes calldata data,
                    address fallbackHandler,
                    address paymentToken,
                    uint256 payment,
                    address payable paymentReceiver
                ) external {
                    // setupOwners checks if the Threshold is already set, therefore preventing that this method is called twice
                    setupOwners(_owners, _threshold);
                    if (fallbackHandler != address(0)) internalSetFallbackHandler(fallbackHandler);
                    // As setupOwners can only be called if the contract has not been initialized we don't need a check for setupModules
                    setupModules(to, data);
                    if (payment > 0) {
                        // To avoid running into issues with EIP-170 we reuse the handlePayment function (to avoid adjusting code of that has been verified we do not adjust the method itself)
                        // baseGas = 0, gasPrice = 1 and gas = payment => amount = (payment + 0) * 1 = payment
                        handlePayment(payment, 0, 1, paymentToken, paymentReceiver);
                    }
                    emit SafeSetup(msg.sender, _owners, _threshold, to, fallbackHandler);
                }
                /// @dev Allows to execute a Safe transaction confirmed by required number of owners and then pays the account that submitted the transaction.
                ///      Note: The fees are always transferred, even if the user transaction fails.
                /// @param to Destination address of Safe transaction.
                /// @param value Ether value of Safe transaction.
                /// @param data Data payload of Safe transaction.
                /// @param operation Operation type of Safe transaction.
                /// @param safeTxGas Gas that should be used for the Safe transaction.
                /// @param baseGas Gas costs that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund)
                /// @param gasPrice Gas price that should be used for the payment calculation.
                /// @param gasToken Token address (or 0 if ETH) that is used for the payment.
                /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin).
                /// @param signatures Packed signature data ({bytes32 r}{bytes32 s}{uint8 v})
                function execTransaction(
                    address to,
                    uint256 value,
                    bytes calldata data,
                    Enum.Operation operation,
                    uint256 safeTxGas,
                    uint256 baseGas,
                    uint256 gasPrice,
                    address gasToken,
                    address payable refundReceiver,
                    bytes memory signatures
                ) public payable virtual returns (bool success) {
                    bytes32 txHash;
                    // Use scope here to limit variable lifetime and prevent `stack too deep` errors
                    {
                        bytes memory txHashData =
                            encodeTransactionData(
                                // Transaction info
                                to,
                                value,
                                data,
                                operation,
                                safeTxGas,
                                // Payment info
                                baseGas,
                                gasPrice,
                                gasToken,
                                refundReceiver,
                                // Signature info
                                nonce
                            );
                        // Increase nonce and execute transaction.
                        nonce++;
                        txHash = keccak256(txHashData);
                        checkSignatures(txHash, txHashData, signatures);
                    }
                    address guard = getGuard();
                    {
                        if (guard != address(0)) {
                            Guard(guard).checkTransaction(
                                // Transaction info
                                to,
                                value,
                                data,
                                operation,
                                safeTxGas,
                                // Payment info
                                baseGas,
                                gasPrice,
                                gasToken,
                                refundReceiver,
                                // Signature info
                                signatures,
                                msg.sender
                            );
                        }
                    }
                    // We require some gas to emit the events (at least 2500) after the execution and some to perform code until the execution (500)
                    // We also include the 1/64 in the check that is not send along with a call to counteract potential shortings because of EIP-150
                    require(gasleft() >= ((safeTxGas * 64) / 63).max(safeTxGas + 2500) + 500, "GS010");
                    // Use scope here to limit variable lifetime and prevent `stack too deep` errors
                    {
                        uint256 gasUsed = gasleft();
                        // If the gasPrice is 0 we assume that nearly all available gas can be used (it is always more than safeTxGas)
                        // We only substract 2500 (compared to the 3000 before) to ensure that the amount passed is still higher than safeTxGas
                        success = execute(to, value, data, operation, gasPrice == 0 ? (gasleft() - 2500) : safeTxGas);
                        gasUsed = gasUsed.sub(gasleft());
                        // If no safeTxGas and no gasPrice was set (e.g. both are 0), then the internal tx is required to be successful
                        // This makes it possible to use `estimateGas` without issues, as it searches for the minimum gas where the tx doesn't revert
                        require(success || safeTxGas != 0 || gasPrice != 0, "GS013");
                        // We transfer the calculated tx costs to the tx.origin to avoid sending it to intermediate contracts that have made calls
                        uint256 payment = 0;
                        if (gasPrice > 0) {
                            payment = handlePayment(gasUsed, baseGas, gasPrice, gasToken, refundReceiver);
                        }
                        if (success) emit ExecutionSuccess(txHash, payment);
                        else emit ExecutionFailure(txHash, payment);
                    }
                    {
                        if (guard != address(0)) {
                            Guard(guard).checkAfterExecution(txHash, success);
                        }
                    }
                }
                function handlePayment(
                    uint256 gasUsed,
                    uint256 baseGas,
                    uint256 gasPrice,
                    address gasToken,
                    address payable refundReceiver
                ) private returns (uint256 payment) {
                    // solhint-disable-next-line avoid-tx-origin
                    address payable receiver = refundReceiver == address(0) ? payable(tx.origin) : refundReceiver;
                    if (gasToken == address(0)) {
                        // For ETH we will only adjust the gas price to not be higher than the actual used gas price
                        payment = gasUsed.add(baseGas).mul(gasPrice < tx.gasprice ? gasPrice : tx.gasprice);
                        require(receiver.send(payment), "GS011");
                    } else {
                        payment = gasUsed.add(baseGas).mul(gasPrice);
                        require(transferToken(gasToken, receiver, payment), "GS012");
                    }
                }
                /**
                 * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise.
                 * @param dataHash Hash of the data (could be either a message hash or transaction hash)
                 * @param data That should be signed (this is passed to an external validator contract)
                 * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash.
                 */
                function checkSignatures(
                    bytes32 dataHash,
                    bytes memory data,
                    bytes memory signatures
                ) public view {
                    // Load threshold to avoid multiple storage loads
                    uint256 _threshold = threshold;
                    // Check that a threshold is set
                    require(_threshold > 0, "GS001");
                    checkNSignatures(dataHash, data, signatures, _threshold);
                }
                /**
                 * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise.
                 * @param dataHash Hash of the data (could be either a message hash or transaction hash)
                 * @param data That should be signed (this is passed to an external validator contract)
                 * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash.
                 * @param requiredSignatures Amount of required valid signatures.
                 */
                function checkNSignatures(
                    bytes32 dataHash,
                    bytes memory data,
                    bytes memory signatures,
                    uint256 requiredSignatures
                ) public view {
                    // Check that the provided signature data is not too short
                    require(signatures.length >= requiredSignatures.mul(65), "GS020");
                    // There cannot be an owner with address 0.
                    address lastOwner = address(0);
                    address currentOwner;
                    uint8 v;
                    bytes32 r;
                    bytes32 s;
                    uint256 i;
                    for (i = 0; i < requiredSignatures; i++) {
                        (v, r, s) = signatureSplit(signatures, i);
                        if (v == 0) {
                            // If v is 0 then it is a contract signature
                            // When handling contract signatures the address of the contract is encoded into r
                            currentOwner = address(uint160(uint256(r)));
                            // Check that signature data pointer (s) is not pointing inside the static part of the signatures bytes
                            // This check is not completely accurate, since it is possible that more signatures than the threshold are send.
                            // Here we only check that the pointer is not pointing inside the part that is being processed
                            require(uint256(s) >= requiredSignatures.mul(65), "GS021");
                            // Check that signature data pointer (s) is in bounds (points to the length of data -> 32 bytes)
                            require(uint256(s).add(32) <= signatures.length, "GS022");
                            // Check if the contract signature is in bounds: start of data is s + 32 and end is start + signature length
                            uint256 contractSignatureLen;
                            // solhint-disable-next-line no-inline-assembly
                            assembly {
                                contractSignatureLen := mload(add(add(signatures, s), 0x20))
                            }
                            require(uint256(s).add(32).add(contractSignatureLen) <= signatures.length, "GS023");
                            // Check signature
                            bytes memory contractSignature;
                            // solhint-disable-next-line no-inline-assembly
                            assembly {
                                // The signature data for contract signatures is appended to the concatenated signatures and the offset is stored in s
                                contractSignature := add(add(signatures, s), 0x20)
                            }
                            require(ISignatureValidator(currentOwner).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "GS024");
                        } else if (v == 1) {
                            // If v is 1 then it is an approved hash
                            // When handling approved hashes the address of the approver is encoded into r
                            currentOwner = address(uint160(uint256(r)));
                            // Hashes are automatically approved by the sender of the message or when they have been pre-approved via a separate transaction
                            require(msg.sender == currentOwner || approvedHashes[currentOwner][dataHash] != 0, "GS025");
                        } else if (v > 30) {
                            // If v > 30 then default va (27,28) has been adjusted for eth_sign flow
                            // To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix before applying ecrecover
                            currentOwner = ecrecover(keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
            32", dataHash)), v - 4, r, s);
                        } else {
                            // Default is the ecrecover flow with the provided data hash
                            // Use ecrecover with the messageHash for EOA signatures
                            currentOwner = ecrecover(dataHash, v, r, s);
                        }
                        require(currentOwner > lastOwner && owners[currentOwner] != address(0) && currentOwner != SENTINEL_OWNERS, "GS026");
                        lastOwner = currentOwner;
                    }
                }
                /// @dev Allows to estimate a Safe transaction.
                ///      This method is only meant for estimation purpose, therefore the call will always revert and encode the result in the revert data.
                ///      Since the `estimateGas` function includes refunds, call this method to get an estimated of the costs that are deducted from the safe with `execTransaction`
                /// @param to Destination address of Safe transaction.
                /// @param value Ether value of Safe transaction.
                /// @param data Data payload of Safe transaction.
                /// @param operation Operation type of Safe transaction.
                /// @return Estimate without refunds and overhead fees (base transaction and payload data gas costs).
                /// @notice Deprecated in favor of common/StorageAccessible.sol and will be removed in next version.
                function requiredTxGas(
                    address to,
                    uint256 value,
                    bytes calldata data,
                    Enum.Operation operation
                ) external returns (uint256) {
                    uint256 startGas = gasleft();
                    // We don't provide an error message here, as we use it to return the estimate
                    require(execute(to, value, data, operation, gasleft()));
                    uint256 requiredGas = startGas - gasleft();
                    // Convert response to string and return via error message
                    revert(string(abi.encodePacked(requiredGas)));
                }
                /**
                 * @dev Marks a hash as approved. This can be used to validate a hash that is used by a signature.
                 * @param hashToApprove The hash that should be marked as approved for signatures that are verified by this contract.
                 */
                function approveHash(bytes32 hashToApprove) external {
                    require(owners[msg.sender] != address(0), "GS030");
                    approvedHashes[msg.sender][hashToApprove] = 1;
                    emit ApproveHash(hashToApprove, msg.sender);
                }
                /// @dev Returns the chain id used by this contract.
                function getChainId() public view returns (uint256) {
                    uint256 id;
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        id := chainid()
                    }
                    return id;
                }
                function domainSeparator() public view returns (bytes32) {
                    return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this));
                }
                /// @dev Returns the bytes that are hashed to be signed by owners.
                /// @param to Destination address.
                /// @param value Ether value.
                /// @param data Data payload.
                /// @param operation Operation type.
                /// @param safeTxGas Gas that should be used for the safe transaction.
                /// @param baseGas Gas costs for that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund)
                /// @param gasPrice Maximum gas price that should be used for this transaction.
                /// @param gasToken Token address (or 0 if ETH) that is used for the payment.
                /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin).
                /// @param _nonce Transaction nonce.
                /// @return Transaction hash bytes.
                function encodeTransactionData(
                    address to,
                    uint256 value,
                    bytes calldata data,
                    Enum.Operation operation,
                    uint256 safeTxGas,
                    uint256 baseGas,
                    uint256 gasPrice,
                    address gasToken,
                    address refundReceiver,
                    uint256 _nonce
                ) public view returns (bytes memory) {
                    bytes32 safeTxHash =
                        keccak256(
                            abi.encode(
                                SAFE_TX_TYPEHASH,
                                to,
                                value,
                                keccak256(data),
                                operation,
                                safeTxGas,
                                baseGas,
                                gasPrice,
                                gasToken,
                                refundReceiver,
                                _nonce
                            )
                        );
                    return abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator(), safeTxHash);
                }
                /// @dev Returns hash to be signed by owners.
                /// @param to Destination address.
                /// @param value Ether value.
                /// @param data Data payload.
                /// @param operation Operation type.
                /// @param safeTxGas Fas that should be used for the safe transaction.
                /// @param baseGas Gas costs for data used to trigger the safe transaction.
                /// @param gasPrice Maximum gas price that should be used for this transaction.
                /// @param gasToken Token address (or 0 if ETH) that is used for the payment.
                /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin).
                /// @param _nonce Transaction nonce.
                /// @return Transaction hash.
                function getTransactionHash(
                    address to,
                    uint256 value,
                    bytes calldata data,
                    Enum.Operation operation,
                    uint256 safeTxGas,
                    uint256 baseGas,
                    uint256 gasPrice,
                    address gasToken,
                    address refundReceiver,
                    uint256 _nonce
                ) public view returns (bytes32) {
                    return keccak256(encodeTransactionData(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce));
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            import "../common/Enum.sol";
            /// @title Executor - A contract that can execute transactions
            /// @author Richard Meissner - <[email protected]>
            contract Executor {
                function execute(
                    address to,
                    uint256 value,
                    bytes memory data,
                    Enum.Operation operation,
                    uint256 txGas
                ) internal returns (bool success) {
                    if (operation == Enum.Operation.DelegateCall) {
                        // solhint-disable-next-line no-inline-assembly
                        assembly {
                            success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0)
                        }
                    } else {
                        // solhint-disable-next-line no-inline-assembly
                        assembly {
                            success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0)
                        }
                    }
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            import "../common/SelfAuthorized.sol";
            /// @title Fallback Manager - A contract that manages fallback calls made to this contract
            /// @author Richard Meissner - <[email protected]>
            contract FallbackManager is SelfAuthorized {
                event ChangedFallbackHandler(address handler);
                // keccak256("fallback_manager.handler.address")
                bytes32 internal constant FALLBACK_HANDLER_STORAGE_SLOT = 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5;
                function internalSetFallbackHandler(address handler) internal {
                    bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT;
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        sstore(slot, handler)
                    }
                }
                /// @dev Allows to add a contract to handle fallback calls.
                ///      Only fallback calls without value and with data will be forwarded.
                ///      This can only be done via a Safe transaction.
                /// @param handler contract to handle fallbacks calls.
                function setFallbackHandler(address handler) public authorized {
                    internalSetFallbackHandler(handler);
                    emit ChangedFallbackHandler(handler);
                }
                // solhint-disable-next-line payable-fallback,no-complex-fallback
                fallback() external {
                    bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT;
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        let handler := sload(slot)
                        if iszero(handler) {
                            return(0, 0)
                        }
                        calldatacopy(0, 0, calldatasize())
                        // The msg.sender address is shifted to the left by 12 bytes to remove the padding
                        // Then the address without padding is stored right after the calldata
                        mstore(calldatasize(), shl(96, caller()))
                        // Add 20 bytes for the address appended add the end
                        let success := call(gas(), handler, 0, 0, add(calldatasize(), 20), 0, 0)
                        returndatacopy(0, 0, returndatasize())
                        if iszero(success) {
                            revert(0, returndatasize())
                        }
                        return(0, returndatasize())
                    }
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            import "../common/Enum.sol";
            import "../common/SelfAuthorized.sol";
            interface Guard {
                function checkTransaction(
                    address to,
                    uint256 value,
                    bytes memory data,
                    Enum.Operation operation,
                    uint256 safeTxGas,
                    uint256 baseGas,
                    uint256 gasPrice,
                    address gasToken,
                    address payable refundReceiver,
                    bytes memory signatures,
                    address msgSender
                ) external;
                function checkAfterExecution(bytes32 txHash, bool success) external;
            }
            /// @title Fallback Manager - A contract that manages fallback calls made to this contract
            /// @author Richard Meissner - <[email protected]>
            contract GuardManager is SelfAuthorized {
                event ChangedGuard(address guard);
                // keccak256("guard_manager.guard.address")
                bytes32 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8;
                /// @dev Set a guard that checks transactions before execution
                /// @param guard The address of the guard to be used or the 0 address to disable the guard
                function setGuard(address guard) external authorized {
                    bytes32 slot = GUARD_STORAGE_SLOT;
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        sstore(slot, guard)
                    }
                    emit ChangedGuard(guard);
                }
                function getGuard() internal view returns (address guard) {
                    bytes32 slot = GUARD_STORAGE_SLOT;
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        guard := sload(slot)
                    }
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            import "../common/Enum.sol";
            import "../common/SelfAuthorized.sol";
            import "./Executor.sol";
            /// @title Module Manager - A contract that manages modules that can execute transactions via this contract
            /// @author Stefan George - <[email protected]>
            /// @author Richard Meissner - <[email protected]>
            contract ModuleManager is SelfAuthorized, Executor {
                event EnabledModule(address module);
                event DisabledModule(address module);
                event ExecutionFromModuleSuccess(address indexed module);
                event ExecutionFromModuleFailure(address indexed module);
                address internal constant SENTINEL_MODULES = address(0x1);
                mapping(address => address) internal modules;
                function setupModules(address to, bytes memory data) internal {
                    require(modules[SENTINEL_MODULES] == address(0), "GS100");
                    modules[SENTINEL_MODULES] = SENTINEL_MODULES;
                    if (to != address(0))
                        // Setup has to complete successfully or transaction fails.
                        require(execute(to, 0, data, Enum.Operation.DelegateCall, gasleft()), "GS000");
                }
                /// @dev Allows to add a module to the whitelist.
                ///      This can only be done via a Safe transaction.
                /// @notice Enables the module `module` for the Safe.
                /// @param module Module to be whitelisted.
                function enableModule(address module) public authorized {
                    // Module address cannot be null or sentinel.
                    require(module != address(0) && module != SENTINEL_MODULES, "GS101");
                    // Module cannot be added twice.
                    require(modules[module] == address(0), "GS102");
                    modules[module] = modules[SENTINEL_MODULES];
                    modules[SENTINEL_MODULES] = module;
                    emit EnabledModule(module);
                }
                /// @dev Allows to remove a module from the whitelist.
                ///      This can only be done via a Safe transaction.
                /// @notice Disables the module `module` for the Safe.
                /// @param prevModule Module that pointed to the module to be removed in the linked list
                /// @param module Module to be removed.
                function disableModule(address prevModule, address module) public authorized {
                    // Validate module address and check that it corresponds to module index.
                    require(module != address(0) && module != SENTINEL_MODULES, "GS101");
                    require(modules[prevModule] == module, "GS103");
                    modules[prevModule] = modules[module];
                    modules[module] = address(0);
                    emit DisabledModule(module);
                }
                /// @dev Allows a Module to execute a Safe transaction without any further confirmations.
                /// @param to Destination address of module transaction.
                /// @param value Ether value of module transaction.
                /// @param data Data payload of module transaction.
                /// @param operation Operation type of module transaction.
                function execTransactionFromModule(
                    address to,
                    uint256 value,
                    bytes memory data,
                    Enum.Operation operation
                ) public virtual returns (bool success) {
                    // Only whitelisted modules are allowed.
                    require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "GS104");
                    // Execute transaction without further confirmations.
                    success = execute(to, value, data, operation, gasleft());
                    if (success) emit ExecutionFromModuleSuccess(msg.sender);
                    else emit ExecutionFromModuleFailure(msg.sender);
                }
                /// @dev Allows a Module to execute a Safe transaction without any further confirmations and return data
                /// @param to Destination address of module transaction.
                /// @param value Ether value of module transaction.
                /// @param data Data payload of module transaction.
                /// @param operation Operation type of module transaction.
                function execTransactionFromModuleReturnData(
                    address to,
                    uint256 value,
                    bytes memory data,
                    Enum.Operation operation
                ) public returns (bool success, bytes memory returnData) {
                    success = execTransactionFromModule(to, value, data, operation);
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        // Load free memory location
                        let ptr := mload(0x40)
                        // We allocate memory for the return data by setting the free memory location to
                        // current free memory location + data size + 32 bytes for data size value
                        mstore(0x40, add(ptr, add(returndatasize(), 0x20)))
                        // Store the size
                        mstore(ptr, returndatasize())
                        // Store the data
                        returndatacopy(add(ptr, 0x20), 0, returndatasize())
                        // Point the return data to the correct memory location
                        returnData := ptr
                    }
                }
                /// @dev Returns if an module is enabled
                /// @return True if the module is enabled
                function isModuleEnabled(address module) public view returns (bool) {
                    return SENTINEL_MODULES != module && modules[module] != address(0);
                }
                /// @dev Returns array of modules.
                /// @param start Start of the page.
                /// @param pageSize Maximum number of modules that should be returned.
                /// @return array Array of modules.
                /// @return next Start of the next page.
                function getModulesPaginated(address start, uint256 pageSize) external view returns (address[] memory array, address next) {
                    // Init array with max page size
                    array = new address[](pageSize);
                    // Populate return array
                    uint256 moduleCount = 0;
                    address currentModule = modules[start];
                    while (currentModule != address(0x0) && currentModule != SENTINEL_MODULES && moduleCount < pageSize) {
                        array[moduleCount] = currentModule;
                        currentModule = modules[currentModule];
                        moduleCount++;
                    }
                    next = currentModule;
                    // Set correct size of returned array
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        mstore(array, moduleCount)
                    }
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            import "../common/SelfAuthorized.sol";
            /// @title OwnerManager - Manages a set of owners and a threshold to perform actions.
            /// @author Stefan George - <[email protected]>
            /// @author Richard Meissner - <[email protected]>
            contract OwnerManager is SelfAuthorized {
                event AddedOwner(address owner);
                event RemovedOwner(address owner);
                event ChangedThreshold(uint256 threshold);
                address internal constant SENTINEL_OWNERS = address(0x1);
                mapping(address => address) internal owners;
                uint256 internal ownerCount;
                uint256 internal threshold;
                /// @dev Setup function sets initial storage of contract.
                /// @param _owners List of Safe owners.
                /// @param _threshold Number of required confirmations for a Safe transaction.
                function setupOwners(address[] memory _owners, uint256 _threshold) internal {
                    // Threshold can only be 0 at initialization.
                    // Check ensures that setup function can only be called once.
                    require(threshold == 0, "GS200");
                    // Validate that threshold is smaller than number of added owners.
                    require(_threshold <= _owners.length, "GS201");
                    // There has to be at least one Safe owner.
                    require(_threshold >= 1, "GS202");
                    // Initializing Safe owners.
                    address currentOwner = SENTINEL_OWNERS;
                    for (uint256 i = 0; i < _owners.length; i++) {
                        // Owner address cannot be null.
                        address owner = _owners[i];
                        require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this) && currentOwner != owner, "GS203");
                        // No duplicate owners allowed.
                        require(owners[owner] == address(0), "GS204");
                        owners[currentOwner] = owner;
                        currentOwner = owner;
                    }
                    owners[currentOwner] = SENTINEL_OWNERS;
                    ownerCount = _owners.length;
                    threshold = _threshold;
                }
                /// @dev Allows to add a new owner to the Safe and update the threshold at the same time.
                ///      This can only be done via a Safe transaction.
                /// @notice Adds the owner `owner` to the Safe and updates the threshold to `_threshold`.
                /// @param owner New owner address.
                /// @param _threshold New threshold.
                function addOwnerWithThreshold(address owner, uint256 _threshold) public authorized {
                    // Owner address cannot be null, the sentinel or the Safe itself.
                    require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this), "GS203");
                    // No duplicate owners allowed.
                    require(owners[owner] == address(0), "GS204");
                    owners[owner] = owners[SENTINEL_OWNERS];
                    owners[SENTINEL_OWNERS] = owner;
                    ownerCount++;
                    emit AddedOwner(owner);
                    // Change threshold if threshold was changed.
                    if (threshold != _threshold) changeThreshold(_threshold);
                }
                /// @dev Allows to remove an owner from the Safe and update the threshold at the same time.
                ///      This can only be done via a Safe transaction.
                /// @notice Removes the owner `owner` from the Safe and updates the threshold to `_threshold`.
                /// @param prevOwner Owner that pointed to the owner to be removed in the linked list
                /// @param owner Owner address to be removed.
                /// @param _threshold New threshold.
                function removeOwner(
                    address prevOwner,
                    address owner,
                    uint256 _threshold
                ) public authorized {
                    // Only allow to remove an owner, if threshold can still be reached.
                    require(ownerCount - 1 >= _threshold, "GS201");
                    // Validate owner address and check that it corresponds to owner index.
                    require(owner != address(0) && owner != SENTINEL_OWNERS, "GS203");
                    require(owners[prevOwner] == owner, "GS205");
                    owners[prevOwner] = owners[owner];
                    owners[owner] = address(0);
                    ownerCount--;
                    emit RemovedOwner(owner);
                    // Change threshold if threshold was changed.
                    if (threshold != _threshold) changeThreshold(_threshold);
                }
                /// @dev Allows to swap/replace an owner from the Safe with another address.
                ///      This can only be done via a Safe transaction.
                /// @notice Replaces the owner `oldOwner` in the Safe with `newOwner`.
                /// @param prevOwner Owner that pointed to the owner to be replaced in the linked list
                /// @param oldOwner Owner address to be replaced.
                /// @param newOwner New owner address.
                function swapOwner(
                    address prevOwner,
                    address oldOwner,
                    address newOwner
                ) public authorized {
                    // Owner address cannot be null, the sentinel or the Safe itself.
                    require(newOwner != address(0) && newOwner != SENTINEL_OWNERS && newOwner != address(this), "GS203");
                    // No duplicate owners allowed.
                    require(owners[newOwner] == address(0), "GS204");
                    // Validate oldOwner address and check that it corresponds to owner index.
                    require(oldOwner != address(0) && oldOwner != SENTINEL_OWNERS, "GS203");
                    require(owners[prevOwner] == oldOwner, "GS205");
                    owners[newOwner] = owners[oldOwner];
                    owners[prevOwner] = newOwner;
                    owners[oldOwner] = address(0);
                    emit RemovedOwner(oldOwner);
                    emit AddedOwner(newOwner);
                }
                /// @dev Allows to update the number of required confirmations by Safe owners.
                ///      This can only be done via a Safe transaction.
                /// @notice Changes the threshold of the Safe to `_threshold`.
                /// @param _threshold New threshold.
                function changeThreshold(uint256 _threshold) public authorized {
                    // Validate that threshold is smaller than number of owners.
                    require(_threshold <= ownerCount, "GS201");
                    // There has to be at least one Safe owner.
                    require(_threshold >= 1, "GS202");
                    threshold = _threshold;
                    emit ChangedThreshold(threshold);
                }
                function getThreshold() public view returns (uint256) {
                    return threshold;
                }
                function isOwner(address owner) public view returns (bool) {
                    return owner != SENTINEL_OWNERS && owners[owner] != address(0);
                }
                /// @dev Returns array of owners.
                /// @return Array of Safe owners.
                function getOwners() public view returns (address[] memory) {
                    address[] memory array = new address[](ownerCount);
                    // populate return array
                    uint256 index = 0;
                    address currentOwner = owners[SENTINEL_OWNERS];
                    while (currentOwner != SENTINEL_OWNERS) {
                        array[index] = currentOwner;
                        currentOwner = owners[currentOwner];
                        index++;
                    }
                    return array;
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            /// @title Enum - Collection of enums
            /// @author Richard Meissner - <[email protected]>
            contract Enum {
                enum Operation {Call, DelegateCall}
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            /// @title EtherPaymentFallback - A contract that has a fallback to accept ether payments
            /// @author Richard Meissner - <[email protected]>
            contract EtherPaymentFallback {
                event SafeReceived(address indexed sender, uint256 value);
                /// @dev Fallback function accepts Ether transactions.
                receive() external payable {
                    emit SafeReceived(msg.sender, msg.value);
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            /// @title SecuredTokenTransfer - Secure token transfer
            /// @author Richard Meissner - <[email protected]>
            contract SecuredTokenTransfer {
                /// @dev Transfers a token and returns if it was a success
                /// @param token Token that should be transferred
                /// @param receiver Receiver to whom the token should be transferred
                /// @param amount The amount of tokens that should be transferred
                function transferToken(
                    address token,
                    address receiver,
                    uint256 amount
                ) internal returns (bool transferred) {
                    // 0xa9059cbb - keccack("transfer(address,uint256)")
                    bytes memory data = abi.encodeWithSelector(0xa9059cbb, receiver, amount);
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        // We write the return value to scratch space.
                        // See https://docs.soliditylang.org/en/v0.7.6/internals/layout_in_memory.html#layout-in-memory
                        let success := call(sub(gas(), 10000), token, 0, add(data, 0x20), mload(data), 0, 0x20)
                        switch returndatasize()
                            case 0 {
                                transferred := success
                            }
                            case 0x20 {
                                transferred := iszero(or(iszero(success), iszero(mload(0))))
                            }
                            default {
                                transferred := 0
                            }
                    }
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            /// @title SelfAuthorized - authorizes current contract to perform actions
            /// @author Richard Meissner - <[email protected]>
            contract SelfAuthorized {
                function requireSelfCall() private view {
                    require(msg.sender == address(this), "GS031");
                }
                modifier authorized() {
                    // This is a function call as it minimized the bytecode size
                    requireSelfCall();
                    _;
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            /// @title SignatureDecoder - Decodes signatures that a encoded as bytes
            /// @author Richard Meissner - <[email protected]>
            contract SignatureDecoder {
                /// @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s`.
                /// @notice Make sure to peform a bounds check for @param pos, to avoid out of bounds access on @param signatures
                /// @param pos which signature to read. A prior bounds check of this parameter should be performed, to avoid out of bounds access
                /// @param signatures concatenated rsv signatures
                function signatureSplit(bytes memory signatures, uint256 pos)
                    internal
                    pure
                    returns (
                        uint8 v,
                        bytes32 r,
                        bytes32 s
                    )
                {
                    // The signature format is a compact form of:
                    //   {bytes32 r}{bytes32 s}{uint8 v}
                    // Compact means, uint8 is not padded to 32 bytes.
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        let signaturePos := mul(0x41, pos)
                        r := mload(add(signatures, add(signaturePos, 0x20)))
                        s := mload(add(signatures, add(signaturePos, 0x40)))
                        // Here we are loading the last 32 bytes, including 31 bytes
                        // of 's'. There is no 'mload8' to do this.
                        //
                        // 'byte' is not working due to the Solidity parser, so lets
                        // use the second best option, 'and'
                        v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff)
                    }
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            /// @title Singleton - Base for singleton contracts (should always be first super contract)
            ///         This contract is tightly coupled to our proxy contract (see `proxies/GnosisSafeProxy.sol`)
            /// @author Richard Meissner - <[email protected]>
            contract Singleton {
                // singleton always needs to be first declared variable, to ensure that it is at the same location as in the Proxy contract.
                // It should also always be ensured that the address is stored alone (uses a full word)
                address private singleton;
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            /// @title StorageAccessible - generic base contract that allows callers to access all internal storage.
            /// @notice See https://github.com/gnosis/util-contracts/blob/bb5fe5fb5df6d8400998094fb1b32a178a47c3a1/contracts/StorageAccessible.sol
            contract StorageAccessible {
                /**
                 * @dev Reads `length` bytes of storage in the currents contract
                 * @param offset - the offset in the current contract's storage in words to start reading from
                 * @param length - the number of words (32 bytes) of data to read
                 * @return the bytes that were read.
                 */
                function getStorageAt(uint256 offset, uint256 length) public view returns (bytes memory) {
                    bytes memory result = new bytes(length * 32);
                    for (uint256 index = 0; index < length; index++) {
                        // solhint-disable-next-line no-inline-assembly
                        assembly {
                            let word := sload(add(offset, index))
                            mstore(add(add(result, 0x20), mul(index, 0x20)), word)
                        }
                    }
                    return result;
                }
                /**
                 * @dev Performs a delegetecall on a targetContract in the context of self.
                 * Internally reverts execution to avoid side effects (making it static).
                 *
                 * This method reverts with data equal to `abi.encode(bool(success), bytes(response))`.
                 * Specifically, the `returndata` after a call to this method will be:
                 * `success:bool || response.length:uint256 || response:bytes`.
                 *
                 * @param targetContract Address of the contract containing the code to execute.
                 * @param calldataPayload Calldata that should be sent to the target contract (encoded method name and arguments).
                 */
                function simulateAndRevert(address targetContract, bytes memory calldataPayload) external {
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        let success := delegatecall(gas(), targetContract, add(calldataPayload, 0x20), mload(calldataPayload), 0, 0)
                        mstore(0x00, success)
                        mstore(0x20, returndatasize())
                        returndatacopy(0x40, 0, returndatasize())
                        revert(0, add(returndatasize(), 0x40))
                    }
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            /**
             * @title GnosisSafeMath
             * @dev Math operations with safety checks that revert on error
             * Renamed from SafeMath to GnosisSafeMath to avoid conflicts
             * TODO: remove once open zeppelin update to solc 0.5.0
             */
            library GnosisSafeMath {
                /**
                 * @dev Multiplies two numbers, reverts on overflow.
                 */
                function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                    // benefit is lost if 'b' is also tested.
                    // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
                    if (a == 0) {
                        return 0;
                    }
                    uint256 c = a * b;
                    require(c / a == b);
                    return c;
                }
                /**
                 * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
                 */
                function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                    require(b <= a);
                    uint256 c = a - b;
                    return c;
                }
                /**
                 * @dev Adds two numbers, reverts on overflow.
                 */
                function add(uint256 a, uint256 b) internal pure returns (uint256) {
                    uint256 c = a + b;
                    require(c >= a);
                    return c;
                }
                /**
                 * @dev Returns the largest of two numbers.
                 */
                function max(uint256 a, uint256 b) internal pure returns (uint256) {
                    return a >= b ? a : b;
                }
            }
            // SPDX-License-Identifier: LGPL-3.0-only
            pragma solidity >=0.7.0 <0.9.0;
            contract ISignatureValidatorConstants {
                // bytes4(keccak256("isValidSignature(bytes,bytes)")
                bytes4 internal constant EIP1271_MAGIC_VALUE = 0x20c13b0b;
            }
            abstract contract ISignatureValidator is ISignatureValidatorConstants {
                /**
                 * @dev Should return whether the signature provided is valid for the provided data
                 * @param _data Arbitrary length data signed on the behalf of address(this)
                 * @param _signature Signature byte array associated with _data
                 *
                 * MUST return the bytes4 magic value 0x20c13b0b when function passes.
                 * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
                 * MUST allow external calls
                 */
                function isValidSignature(bytes memory _data, bytes memory _signature) public view virtual returns (bytes4);
            }