// Contract: CryptoBlobs
// Target function: function ownerOf(uint256 tokenId)
nft.coinbase.com/
            // https://etherscan.io/address/0xDef1C0ded9bec7F1a1670819833240f027b25EfF
            0xDef1C0ded9bec7F1a1670819833240f027b25EfF,
            // Element
            // https://element.market/ethereum
            // https://etherscan.io/address/0x20F780A973856B93f63670377900C1d2a50a77c4
            0x20F780A973856B93f63670377900C1d2a50a77c4,
            // SudoSwap
            // https://sudoswap.xyz/
            // https://etherscan.io/address/0x2B2e8cDA09bBA9660dCA5cB6233787738Ad68329
            0x2B2e8cDA09bBA9660dCA5cB6233787738Ad68329,
            // NFTX
            // https://nftx.io/
            // https://etherscan.io/address/0x0fc584529a2aefa997697fafacba5831fac0c22d
            0x0fc584529a2AEfA997697FAfAcbA5831faC0c22d,
            // NFTTrader
            // https://www.nfttrader.io/
            // https://etherscan.io/address/0x657E383EdB9A7407E468acBCc9Fe4C9730c7C275
            0x657E383EdB9A7407E468acBCc9Fe4C9730c7C275
        ];
        for (uint256 i; i < _preapprovedServices.length; i++) {
            _account[_preapprovedServices[i]].preapprovedStatus = 2; // Enable each service.
        }
    }

    modifier nonReentrant() {
        if (_reentrancyStatus == _reentrancyLocked) revert REENTRANT_CALLS_ARE_NOT_ALLOWED();
        _reentrancyStatus = _reentrancyLocked;
        _;
        _reentrancyStatus = _reentrancyUnlocked;
    }

    // Topic: 0xf26aaf0d6935ae39e0b17d4770395a2cf37139301bf4a1550daabaa363cb8df0
    event MintPurchase(
        address account,
        address referrer,
        uint256 data
    );

    // Topic: 0x7236fe0badcff1706816875cc9df7b9b481c3fb939efe1e7967099499db621d9
    event MintFree(
        address account, 
        address contractAddress, 
        uint256 tokenId,
        uint256 data
    );

    // Topic: 0x10d92c47650ef2b2a841f26c951f4391ccbb731e57d36a67665a0d138e08ef09
    event Trade(
        bytes32 indexed hash,
        address indexed maker,
        address indexed taker,
        uint256[] makerIds,
        uint256[] takerIds,
        uint256 price,
        uint256 expiry,
        uint256 timestamp,
        bool isTrade
    );

    // Topic: 0xff06b6e7e3b7963958aa44cc1dff152337abfc3ef2d0ecd54bdcd8fb9694e0eb
    event Optimize(
        address indexed account,
        uint256[] tokenIds,
        uint256 timestamp
    );

    // Topic: 0x062e360bff2a6872f7e8ce922ee6867aaeed320f740365aa0c33bb226d45b034
    event Metadata(
        address indexed account,
        string indexed table,
        string indexed key,
        string value,
        uint256 timestamp
    );

    // Constants - These values cannot be modified.

    /// @notice The symbol of the token.
    string internal constant _symbol = "BLOB";
    /// @notice Max supply of CryptoBlobs.
    uint256 internal constant _maxSupply = 25000;
    /// @notice Total supply when the giveaway ends.
    uint256 internal constant _giveawayEnds = 10000;
    /// @notice Total supply when the sacrifice promo and hourly drops offers ends.
    uint256 internal constant _sacrificeAndHourlyDropsOffersEnds = 20000;
    /// @notice Total supply when the referral program ends.
    uint256 internal constant _referralProgramEnds = 25000;
    /// @notice Maximum number of CryptoBlobs an account can purchase per transaction.
    uint256 internal constant _purchaseLimit = 100;
    /// @notice Maximum number of free CryptoBlobs an account can earn and claim.
    uint256 internal constant _maxFreePerAccount = 10;
    /// @notice Maximum number of NFTs per collection that can be redeemed.
    uint256 internal constant _nftsPerCollectionLimit = 10;
    /// @notice Base price increment per point used on the live market.
    uint256 internal constant _basePriceIncrementPerPoint = 0.0000025 ether;
    /// @notice Price and multiplier decline duration (1% per hour).
    uint256 internal constant _declineDuration = 100 hours;
    /// @notice Each BOGO lasts for 7.5 minutes. Takes 30 minutes for "buy 1, get 1 free".
    uint256 internal constant _bogoDuration = 450 seconds;
    /// @notice BOGO starts at "buy 5, get 1 free".
    uint256 internal constant _bogoStartingFrom = 5;
    /// @notice The number of sacrifices until an account can earn a free CryptoBlob.
    uint256 internal constant _burnsPerReward = 10;
    /// @notice The number of referral purchases until an account can earn a free CryptoBlob.
    uint256 internal constant _referralsPerReward = 5;
    /// @notice The number of purchases until an account starts earning hourly drops.
    uint256 internal constant _hourlyDropThreshold = 5;
    /// @notice Lowers gas when making calculations.
    uint256 internal constant _sections = 8;
    /// @notice Lowers gas when making calculations.
    uint256 internal constant _distance = 3125;
    /// @notice How long each drop takes to earn.
    uint256 internal constant _hourlyDropDuration = 1 hours;
    /// @notice The base amount the temporary demand multiplier fluctuates by.
    uint256 internal constant _volatilityBase = 10;
    /// @notice The amount volatility is multiplied by after the threshold is reached.
    uint256 internal constant _volatilityMultiplier = 3;
    /// @notice Threshold when volatility increases. Causes the price to increase or decrease faster.
    uint256 internal constant _increasedVolatilityThreshold = 20000;
    /// @notice Unlock status for reentrancy guard.
    uint32 internal constant _reentrancyUnlocked = 1;
    /// @notice Lock status for reentrancy guard.
    uint32 internal constant _reentrancyLocked = 2;

    // Public - The public has access to modify these values.

    /// @notice Reentrancy guard status.
    uint32 internal _reentrancyStatus;
    /// @notice The number of CryptoBlobs that have been purchased.
    uint32 internal _tokensMintedPurchase;
    /// @notice The number of CryptoBlobs that have been claimed for free.
    uint32 internal _tokensMintedFree;
    /// @notice The number of CryptoBlobs that have been burned/sacrificed.
    uint32 internal _tokensBurned;
    /// @notice The temporary multiplier on the price, determined by demand and market activity.
    uint32 internal _temporaryDemandMultiplier;
    /// @notice The timestamp the last purchase was made.
    uint32 internal _purchaseTimestamp;
    /// @notice Used NFT IDs in a collection for the giveaway.
    mapping(address => uint256[]) internal _nftsRedeemed;
    /// @notice Stores account data.
    mapping(address => Account) internal _account;
    /// @notice Stores token data.
    mapping(uint256 => Token) internal _token;

    // Owner - SuperCollectiv has access to modify these values using {manageContract}.
    // Additionally, we can modify _contractOwner and _contractCandidate in [Ownable].

    /// @notice The name of the token. We may shorten the name after the initial mint.
    string internal _name = "CryptoBlobs.com | SuperCollectiv";
    /// @notice If the integrated trading platform is paused.
    bool internal _tradingPaused;
    /// @notice If sacrificing is paused.
    bool internal _sacrificingPaused;
    /// @notice The maximum number of souls the token URI will support.
    uint256 internal _URIMaxSoulsLimit = 100;
    /// @notice ENS reverse records address.
    address internal _reverseRecordsAddress = 0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C;
    /// @notice Contract URI for the contract.
    string internal _contractURI;
    /// @notice Token URI prefix for single URI schema.
    string internal _tokenURIPrefix;
    /// @notice Token URI prefixes for multi URI schema.
    mapping(uint256 => string) internal _tokenURIPrefixes;

    /**
        @param tokensOwned The number of CryptoBlobs owned.
        @param mintedPurchase The number of CryptoBlobs purchased.
        @param mintedBogos The number of CryptoBlobs claimed from BOGO deals.
        @param mintedGiveaway The number of CryptoBlobs claimed from the giveaway.
        @param tokensBurned The number of CryptoBlobs burned.
        @param mintedHourlyDrops The number of CryptoBlobs claimed from hourly drops.
        @param mintedSacrifices The number of CryptoBlobs claimed from the sacrifice promo.
        @param mintedReferrals The number of CryptoBlobs claimed from referring.
        @param mintedSpecial The number of CryptoBlobs gifted from SuperCollectiv. Max number is 255.
        @param tokensOptimized The number of CryptoBlobs optimized.
        @param referralPurchases The number of CryptoBlobs purchased with account's referral link.
        @param timestampHourlyDropLastClaimed Timestamp the account last claimed their hourly drop or initialized it.
        @param timestampReferralLinkUsed Timestamp the account used a referral link.
        @param timestampTradesLocked Timestamp the account locked their trades on the integrated trading platform.
        @param timestampTradeHashUsed Timestamp when trade hashes have been cancelled or completed by the account.
        @param approvals Addresses the account has approved to manage all CryptoBlobs owned by them.
    */
    struct Account {
        // Order is optimized.
        uint16 tokensOwned;
        uint16 mintedPurchase;
        uint8 mintedBogos;
        uint8 mintedGiveaway;
        uint16 tokensBurned;
        uint8 mintedHourlyDrops;
        uint8 mintedSacrifices;
        uint8 mintedReferrals;
        uint16 mintedSpecial;
        uint16 tokensOptimized;
        uint16 referralPurchases;
        uint32 timestampHourlyDropLastClaimed;
        uint32 timestampReferralLinkUsed;
        uint32 timestampTradesLocked;
        mapping(bytes32 => uint256) timestampTradeHashUsed;
        mapping(address => uint256) approvals;
        uint256 preapprovedStatus;
    }

    /**
        @param mintTimestamp Timestamp minted.
        @param burnTimestamp Timestamp burned.
        @param souls Before burn: The number of souls harnessed. After burn: The number of souls transferred to burnTo.
        @param burnTo CryptoBlob ID transferred souls to.
        @param account Before burn: Address of the owner. After burn: Address that last owned it.
        @param approval Address of the approved token operator.
    */
    struct Token {
        uint32 mintTimestamp;
        uint32 burnTimestamp;
        uint16 souls;
        uint16 burnTo;
        address account;
        address approval;
    }

    /**
        @notice Batch transaction data for {batch}.
    */
    struct BatchData {
        uint256 category;
        address account1;
        address account2;
        uint256 tokenId;
        uint256[] tokenIds;
        bool approved;
        bytes32 hash;
    }

    /**
        @notice The name of the token.
    */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
        @notice The symbol of the token.
    */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
        @notice If an interface ID is supported. 
        @dev Supported interfaces: IERC165 (0x01ffc9a7), IERC721 (0x80ac58cd), IERC721Metadata (0x5b5e139f), IERC721Enumerable (0x780e9d63).
        @param interfaceId Interface ID to check.
        @return If `interfaceId` is supported.
    */
    function supportsInterface(bytes4 interfaceId)
        public
        view
        virtual
        override
        returns (bool)
    {
        return interfaceId == type(IERC165).interfaceId ||          // 0x01ffc9a7
            interfaceId == type(IERC721).interfaceId ||             // 0x80ac58cd
            interfaceId == type(IERC721Metadata).interfaceId ||     // 0x5b5e139f
            interfaceId == type(IERC721Enumerable).interfaceId;     // 0x780e9d63
    }

    /**
        @notice Returns a token ID owned by `owner` at a given index in its inventory.
        @param owner The address of the account.
        @param index Inventory index of `owner` to read. Uses index zero.
        @return The token ID of the CryptoBlob at the index.
    */
    function tokenOfOwnerByIndex(address owner, uint256 index)
        public
        view
        virtual
        override
        returns (uint256)
    {
        unchecked {
            uint256 tokensMinted = _tokensMinted(); // Saves gas.
            uint256 indexOfOwner; // Iterates when inventory index does not match `index`.
            address accountOfToken;
            uint256 balance = balanceOf(owner);
            if (index < balance) {
                for (uint256 i; i < tokensMinted; i++) {
                    uint256 tokenId = _getTokenId(i);
                    if (_token[tokenId].account != address(0)) {
                        accountOfToken = _token[tokenId].account; // Store address.
                    }
                    // If the CryptoBlob last checked is in the owner's inventory.
                    if (accountOfToken == owner && !_isTokenSacrificed(tokenId)) {
                        if (indexOfOwner != index) {
                            indexOfOwner++; // Iterate if incorrect index.
                        } else {
                            return tokenId; // Return if the index matches.
                        }
                    }
                }
            }
            revert OWNERS_BALANCE_IS_INSUFFICENT_FOR_THE_INDEX();
        }
    }

    /**
        @notice The circulating supply of CryptoBlobs.
    */
    function totalSupply() public view virtual override returns (uint256) {
        unchecked {
            return _tokensMinted() - _tokensBurned; // Cannot underflow.
        }
    }

    /**
        @notice Returns a token ID at a given `index`.
        @param index Index of the circulating supply to read.
        @return The token ID of the CryptoBlob at the index.
    */
    function tokenByIndex(uint256 index)
        public
        view
        virtual
        override
        returns (uint256)
    {
        unchecked {
            uint256 tokensMinted = _tokensMinted();
            uint256 tokenIdsIndex;
            if (index < tokensMinted) {
                // Check each minted CryptoBlob and return the ID when the index is reached.
                for (uint256 i; i < tokensMinted; i++) {
                    uint256 tokenId = _getTokenId(i);
                    if (!_isTokenSacrificed(tokenId)) {
                        if (tokenIdsIndex != index) {
                            tokenIdsIndex++; // Iterate if incorrect index.
                        } else {
                            return tokenId; // Return if the index matches.
                        }
                    }
                }
            }
            revert INVALID_CRYPTOBLOB_ID();
        }
    }

    /**
        @notice The balance of an account.
        @param owner The address of the account.
        @return The balance of `owner`.
    */
    function balanceOf(address owner)
        public
        view
        virtual
        override
        returns (uint256)
    {
        if (owner == address(0)) revert THE_ZERO_ADDRESS_CANNOT_HAVE_AN_ACCOUNT();
        return _account[owner].tokensOwned;
    }