// Contract: CryptoBlobs
// Target function: function _isTokenSacrificed(uint256 tokenId) internal view returns (bool burned)
 claimableSacrifices, 
                        claimableReferrals, 
                        claimableGiveaway
                    );
                
                // REFERRAL PROGRAM (claimableReferrals)
                // Granted when:
                // - under 25K mints
                // - 1 for every 5 purchased by referred accounts
                if ((_account[account].referralPurchases / _referralsPerReward) > _account[account].mintedReferrals) {
                    claimableReferrals = (_account[account].referralPurchases / _referralsPerReward) - _account[account].mintedReferrals;
                    mintedFreeFuture += claimableReferrals;
                    if (mintedFreeFuture >= _maxFreePerAccount) { // If limit reached or surpassed.
                        return (
                            mintedFree, 
                            claimableHourlyDrops, 
                            claimableSacrifices, 
                            claimableReferrals - (mintedFreeFuture - _maxFreePerAccount), // Remove any over limit.
                            claimableGiveaway
                        );
                    }
                }
                
                // Return if 20K+ have been minted.
                if (tokensMinted >= _sacrificeAndHourlyDropsOffersEnds)
                    return (
                        mintedFree, 
                        claimableHourlyDrops, 
                        claimableSacrifices, 
                        claimableReferrals, 
                        claimableGiveaway
                    ); 

                // HOURLY DROP (claimableHourlyDrops)
                // Granted when:
                // - under 20K mints
                // - has been initialized (when 5 or more purchased)
                // - not initialized or claimed hourly drop today
                if (
                    _account[account].timestampHourlyDropLastClaimed != 0 && // If initialized.
                    _account[account].timestampHourlyDropLastClaimed / _hourlyDropDuration != block.timestamp / _hourlyDropDuration // If not initialized or claimed today.
                ) {
                    claimableHourlyDrops = 1; // Grant 1.
                    mintedFreeFuture += claimableHourlyDrops;
                    if (mintedFreeFuture >= _maxFreePerAccount) { // If limit reached or surpassed.
                        return (
                            mintedFree, 
                            claimableHourlyDrops, // Only 1 granted so mintedFreeFuture cannot be > _maxFreePerAccount, only equal to.
                            claimableSacrifices, 
                            claimableReferrals, 
                            claimableGiveaway
                        );
                    }
                }

                // SACRIFICE PROMO (claimableSacrifices)
                // Granted when:
                // - under 20K mints
                // - 1 for every 10 burned
                if ((_account[account].tokensBurned / _burnsPerReward) > _account[account].mintedSacrifices) {
                    claimableSacrifices = (_account[account].tokensBurned / _burnsPerReward) - _account[account].mintedSacrifices;
                    mintedFreeFuture += claimableSacrifices;
                    if (mintedFreeFuture >= _maxFreePerAccount) { // If limit reached or surpassed.
                        return (
                            mintedFree, 
                            claimableHourlyDrops, 
                            claimableSacrifices - (mintedFreeFuture - _maxFreePerAccount), // Remove any over limit.
                            claimableReferrals, 
                            claimableGiveaway
                        );
                    }
                }

                // Return if 10K+ have been minted.
                if (tokensMinted >= _giveawayEnds)
                    return (
                        mintedFree, 
                        claimableHourlyDrops, 
                        claimableSacrifices, 
                        claimableReferrals, 
                        claimableGiveaway
                    ); 

                // GIVEAWAY (claimableGiveaway)
                // Granted when:
                // - under 10K mints
                // - not claimed from the giveaway
                // - NFT redeeming is valid
                if (_account[account].mintedGiveaway == 0) { // If not claimed from the giveaway.
                    claimableGiveaway = 1; // Grant 1.
                    mintedFreeFuture += claimableGiveaway;
                    if (mintedFreeFuture >= _maxFreePerAccount) { // If limit reached or surpassed.
                        return (
                            mintedFree, 
                            claimableHourlyDrops, 
                            claimableSacrifices, 
                            claimableReferrals, 
                            claimableGiveaway // Only 1 granted so mintedFreeFuture cannot be > _maxFreePerAccount, only equal to.
                        );
                    }
                }
            }
            return (mintedFree, claimableHourlyDrops, claimableSacrifices, claimableReferrals, claimableGiveaway);
        }
    }

    /**
        @notice Returns the amount an account minted for free.
        @dev Special mints from SuperCollectiv are not included.
        @param account The address of the account.
        @return mintedFree How many `account` minted for free.
    */
    function _accountToMintedFree(address account) internal view returns (uint256 mintedFree) {
        unchecked {
            mintedFree = (
                _account[account].mintedBogos + 
                _account[account].mintedHourlyDrops + 
                _account[account].mintedSacrifices + 
                _account[account].mintedReferrals + 
                _account[account].mintedGiveaway
                );
            return mintedFree;
        }
    }

    /**
        @notice Check if a trade is approved by the maker and is valid.
        @dev Extracts the public address from a hash and signature pair via ECDSA, then checks if it matches the maker's address.
        @param maker The address of the maker.
        @param hash The hash of the trade.
        @param signature The signature if the signed hash.
        @return approved If `maker` and signer's addresses match.
    */
    function _isTradeApproved(
        address maker,
        bytes32 hash,
        bytes memory signature
    ) internal pure returns (bool approved) {
        unchecked {
            // Initialize
            if (maker == address(0)) return false; // Always false if zero address.
            if (signature.length != 65) return false; // Check length.

            bytes32 r;
            bytes32 s;
            uint8 v;

            // Use assembly to get the signature parameters for ecrecover.
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }

            // If the signature is valid not malleable.
            if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) return false;
            if (v != 27 && v != 28) return false;

            // Calculate the signer.
            // \x19Ethereum Signed Message:\n32 == 0x19457468657265756d205369676e6564204d6573736167653a0a3332
            address signer = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)), v, r, s);
            if (signer == address(0)) return false;

            return (maker == signer); // Check if the addresses match.
        }
    }

    /**
        @notice Mints an amount of CryptoBlobs to an address.
        @dev The contract does not support safeMint to save gas.
        @param to The address of the account to mint the CryptoBlobs to.
        @param purchase The amount of CryptoBlobs to mint to `to` as the purchase mint type.
        @param free The amount of CryptoBlobs to mint to `to` as the free mint type.
        @param safeBlockTimestamp The current timestamp.
        @param tokensMinted The current total number of CryptoBlobs minted.
    */
    function _mint(
        address to,
        uint256 purchase,
        uint256 free,
        uint32 safeBlockTimestamp,
        uint256 tokensMinted
    ) internal {
        unchecked {

            // Initialize mint.
            if (to == address(0)) revert THE_ZERO_ADDRESS_CANNOT_HAVE_AN_ACCOUNT();
            uint256 amount = purchase + free; // Calculate amount minting.
            if (
                amount == 0 || // If minting zero.
                tokensMinted + amount > _maxSupply || // If minting over max supply.
                amount > _purchaseLimit + _maxFreePerAccount // If minting over 110 CryptoBlobs (should never occur).
            ) revert AMOUNT_MINTING_IS_INVALID();

            // Increase the circulating supply.
            if (purchase > 0) _tokensMintedPurchase += uint32(purchase);
            if (free > 0) {
                _tokensMintedFree += uint32(free);
                if (_accountToMintedFree(to) > _maxFreePerAccount) revert AMOUNT_MINTING_IS_INVALID(); // If account has over 10 free mints (should never occur).
            }

            // Update the recipient's balance.
            _account[to].tokensOwned += uint16(amount);

            // Save token data.
            uint256 tokenId = _getTokenId(tokensMinted);
            _token[tokenId].account = to;
            _token[tokenId].mintTimestamp = safeBlockTimestamp;

            // Total mints after this transaction (reuse variable to save gas).
            purchase = tokensMinted + amount;

            // Transfer event for each token.
            while (tokensMinted != purchase) {
                emit Transfer(      // 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
                    address(0),     // address indexed from
                    to,             // address indexed to
                    ((tokensMinted / _sections) % 2 == 0)   // uint256 indexed tokenId
                    ? ((_distance * (tokensMinted % _sections)) + ((_distance * tokensMinted) / _maxSupply) + 1)
                    : (_maxSupply - ((_distance * (tokensMinted % _sections)) + ((_distance * tokensMinted) / _maxSupply)))
                );
                tokensMinted++;
            }
        }
    }

    /**
        @notice Calculates the price and additional market info.
        @dev Price starts at 0 ETH and is determined by supply, demand, and recent market activity.
        @dev Price will be higher if purchased compared to being claimed for free.
        @dev tokensMintedPurchase and tokensMintedFree are sent as parameters to lower gas.
        @param tokensMintedPurchase Number of purchased mints.
        @param tokensMintedFree Number of free mints.
        @return price The current price to purchase a CryptoBlobs in WEI.
        @return base The current base price in WEI.
        @return multiplier The multiplier on the price, which increases/decreases with demand.
        @return decline The percentage the price has declined since the last purchase.
        @return bogo The current BOGO deal determined by demand and market activity.
    */
    function _marketPrice(uint256 tokensMintedPurchase, uint256 tokensMintedFree)
        internal
        view
        returns (
            uint256 price,
            uint256 base,
            uint256 multiplier,
            uint256 decline,
            uint256 bogo
        )
    {
        unchecked {

            // Calculate the base price.
            // +2 points per CryptoBlob purchased.
            // +1 points per CryptoBlob claimed for free.
            // +1 points per CryptoBlob burned.
            // +2 points per CryptoBlob minted after 20,000 mints.
            base = ((tokensMintedPurchase * 2) + tokensMintedFree + _tokensBurned) * _basePriceIncrementPerPoint;
            if (tokensMintedPurchase + tokensMintedFree > _increasedVolatilityThreshold) {
                base += ((tokensMintedPurchase + tokensMintedFree) - _increasedVolatilityThreshold) * _basePriceIncrementPerPoint * _volatilityMultiplier;
            }

            // Calculate the multiplier.
            // The multiplier is determined by demand.
            // Paid mints increases the multiplier by 0.01% and 0.03% after 20,000 for increased volatility.
            // Free mints decreases the multiplier by 0.01% and 0.03% after 20,000 for increased volatility for every 2.
            // Cannot underflow because timestamp cannot decrease and _declineDuration > elapsed is checked.
            uint256 elapsed = block.timestamp - _purchaseTimestamp;
            if (_declineDuration > elapsed) {
                multiplier = ((_declineDuration - elapsed) * _temporaryDemandMultiplier) / _declineDuration;
            }

            // Calculate the percentage declined.
            decline = (elapsed * 100000) / _declineDuration;
            if (decline > 100000) decline = 100000; // Limit decline to 100%.

            // Calculate the price. Cannot underflow because decline is limited to 100%.
            price = ((((base * (multiplier + 100000)) / 100000) * (100000 - decline)) / 100000);

            // Calculate the current BOGO deal.
            bogo = _bogoStartingFrom;
            if (elapsed >= _bogoDuration) {
                bogo = 1;
                if (_bogoStartingFrom > (elapsed / _bogoDuration)) {
                    bogo = _bogoStartingFrom - (elapsed / _bogoDuration);
                }
            }

            return (price, base, multiplier, decline, bogo);
        }
    }

    /**
        @notice Transfers a CryptoBlob from one account to another.
        @dev Ownership is not checked within this function to save gas, instead
        @dev `from` will always be the owner's address whenever this function is called.
        @dev {_transfer} is called by 2 functions: {transferFrom} and {trade}, each time the
        @dev owner's address is retrieved via {_revertIfNoAccess} > {_hasAccess} > {ownerOf}.
        @param from The address that owns `tokenId`.
        @param to The address of the account to transfer `tokenId` to.
        @param tokenId The token ID of the CryptoBlob to transfer.
    */
    function _transfer(address from, address to, uint256 tokenId) internal {
        unchecked {

            // Cannot transfer to the zero address.
            if (to == address(0)) revert THE_ZERO_ADDRESS_CANNOT_HAVE_AN_ACCOUNT();

            // Update owner's stats.
            // Cannot underflow because ownership is checked.
            _account[from].tokensOwned--;
            _account[to].tokensOwned++;

            // Implicitly clear approvals.
            delete _token[tokenId].approval;

            // ERC721 standards says to not emit an Approval event when transferring. https://eips.ethereum.org/EIPS/eip-721
            // "When a Transfer event emits, this also indicates that the approved address for that NFT (if any) is reset to none."

            // Transfer 'tokenId' to 'to'.
            _token[tokenId].account = to;

            emit Transfer(  // 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
                from,       // address indexed from
                to,         // address indexed to
                tokenId     // uint256 indexed tokenId
            );

            // Finalize transfer.
            tokenId = _getTokenIndex(tokenId) + 1;
            if (tokenId < _tokensMinted()) {
                tokenId = _getTokenId(tokenId);
                if (_token[tokenId].account == address(0)) _token[tokenId].account = from;
            }
        }
    }

    /**
        @notice Checks if an address can manage a CryptoBlob and gets the owner's address.
        @dev Owner is returned so {ownerOf} does not have to be called again.
        @dev Use {batchTradeHashUsedAndHasAccess} to access this function.
        @param account The address of the account to check the access status of.
        @param tokenId The token ID of the CryptoBlob.
        @return hasAccess If the address can manage the token.
        @return owner The address of the account that owns `tokenId`.
    */
    function _hasAccess(address account, uint256 tokenId) internal view returns (bool hasAccess, address owner) {
        owner = ownerOf(tokenId); // Get the owner. Reverts if the token does not exist.
        return (
                (
                    account == owner ||                     // If owner.
                    isApprovedForAll(owner, account) ||     // If account operator via {setApprovalForAll}.
                    getApproved(tokenId) == account         // If token operator via {approve}.
                ), 
                owner
            );
    }

    /**
        @notice Reverts if an account does not have access to manage a CryptoBlob ID.
        @param account The address of the account to check the access status of.
        @param tokenId The token ID of the CryptoBlob.
        @return owner The address of the account that owns `tokenId`.
    */
    function _revertIfNoAccess(address account, uint256 tokenId) internal view returns (address owner) {
        (bool hasAccess, address _owner) = _hasAccess(account, tokenId);
        if (!hasAccess) revert RESTRICTED_ACCESS();
        return _owner;
    }

    /**
        @notice Returns the total number of CryptoBlobs minted.
        @return minted The amount of CryptoBlobs minted.
    */
    function _tokensMinted() internal view returns (uint256 minted) {
        unchecked {
            return _tokensMintedPurchase + _tokensMintedFree; // Cannot overflow.
        }
    }