// Contract: AzukiApes
// Target function: function transferFrom(
Of(address addr) external returns (bytes32);
}

pragma solidity ^0.8.13;

/// @notice Optimized and flexible operator filterer to abide to OpenSea's
/// mandatory on-chain royalty enforcement in order for new collections to
/// receive royalties.
/// For more information, see:
/// See: https://github.com/ProjectOpenSea/operator-filter-registry
abstract contract OperatorFilterer {
    /// @dev The default OpenSea operator blocklist subscription.
    address internal constant _DEFAULT_SUBSCRIPTION =
        0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6;

    /// @dev The OpenSea operator filter registry.
    address internal constant _OPERATOR_FILTER_REGISTRY =
        0x000000000000AAeB6D7670E522A718067333cd4E;

    /// @dev Registers the current contract to OpenSea's operator filter,
    /// and subscribe to the default OpenSea operator blocklist.
    /// Note: Will not revert nor update existing settings for repeated registration.
    function _registerForOperatorFiltering() internal virtual {
        _registerForOperatorFiltering(_DEFAULT_SUBSCRIPTION, true);
    }

    /// @dev Registers the current contract to OpenSea's operator filter.
    /// Note: Will not revert nor update existing settings for repeated registration.
    function _registerForOperatorFiltering(
        address subscriptionOrRegistrantToCopy,
        bool subscribe
    ) internal virtual {
        /// @solidity memory-safe-assembly
        assembly {
            let functionSelector := 0x7d3e3dbe // `registerAndSubscribe(address,address)`.

            // Clean the upper 96 bits of `subscriptionOrRegistrantToCopy` in case they are dirty.
            subscriptionOrRegistrantToCopy := shr(
                96,
                shl(96, subscriptionOrRegistrantToCopy)
            )

            for {

            } iszero(subscribe) {

            } {
                if iszero(subscriptionOrRegistrantToCopy) {
                    functionSelector := 0x4420e486 // `register(address)`.
                    break
                }
                functionSelector := 0xa0af2903 // `registerAndCopyEntries(address,address)`.
                break
            }
            // Store the function selector.
            mstore(0x00, shl(224, functionSelector))
            // Store the `address(this)`.
            mstore(0x04, address())
            // Store the `subscriptionOrRegistrantToCopy`.
            mstore(0x24, subscriptionOrRegistrantToCopy)
            // Register into the registry.
            if iszero(
                call(
                    gas(),
                    _OPERATOR_FILTER_REGISTRY,
                    0,
                    0x00,
                    0x44,
                    0x00,
                    0x04
                )
            ) {
                // If the function selector has not been overwritten,
                // it is an out-of-gas error.
                if eq(shr(224, mload(0x00)), functionSelector) {
                    // To prevent gas under-estimation.
                    revert(0, 0)
                }
            }
            // Restore the part of the free memory pointer that was overwritten,
            // which is guaranteed to be zero, because of Solidity's memory size limits.
            mstore(0x24, 0)
        }
    }

    /// @dev Modifier to guard a function and revert if the caller is a blocked operator.
    modifier onlyAllowedOperator(address from) virtual {
        if (from != msg.sender) {
            if (!_isPriorityOperator(msg.sender)) {
                if (_operatorFilteringEnabled()) _revertIfBlocked(msg.sender);
            }
        }
        _;
    }

    /// @dev Modifier to guard a function from approving a blocked operator..
    modifier onlyAllowedOperatorApproval(address operator) virtual {
        if (!_isPriorityOperator(operator)) {
            if (_operatorFilteringEnabled()) _revertIfBlocked(operator);
        }
        _;
    }

    /// @dev Helper function that reverts if the `operator` is blocked by the registry.
    function _revertIfBlocked(address operator) private view {
        /// @solidity memory-safe-assembly
        assembly {
            // Store the function selector of `isOperatorAllowed(address,address)`,
            // shifted left by 6 bytes, which is enough for 8tb of memory.
            // We waste 6-3 = 3 bytes to save on 6 runtime gas (PUSH1 0x224 SHL).
            mstore(0x00, 0xc6171134001122334455)
            // Store the `address(this)`.
            mstore(0x1a, address())
            // Store the `operator`.
            mstore(0x3a, operator)

            // `isOperatorAllowed` always returns true if it does not revert.
            if iszero(
                staticcall(
                    gas(),
                    _OPERATOR_FILTER_REGISTRY,
                    0x16,
                    0x44,
                    0x00,
                    0x00
                )
            ) {
                // Bubble up the revert if the staticcall reverts.
                returndatacopy(0x00, 0x00, returndatasize())
                revert(0x00, returndatasize())
            }

            // We'll skip checking if `from` is inside the blacklist.
            // Even though that can block transferring out of wrapper contracts,
            // we don't want tokens to be stuck.

            // Restore the part of the free memory pointer that was overwritten,
            // which is guaranteed to be zero, if less than 8tb of memory is used.
            mstore(0x3a, 0)
        }
    }

    /// @dev For deriving contracts to override, so that operator filtering
    /// can be turned on / off.
    /// Returns true by default.
    function _operatorFilteringEnabled() internal view virtual returns (bool) {
        return true;
    }

    /// @dev For deriving contracts to override, so that preferred marketplaces can
    /// skip operator filtering, helping users save gas.
    /// Returns false for all inputs by default.
    function _isPriorityOperator(address) internal view virtual returns (bool) {
        return false;
    }
}

/**
 * @dev Interface for the NFT Royalty Standard.
 *
 * A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
 * support for royalty payments across all NFT marketplaces and ecosystem participants.
 *
 * _Available since v4.5._
 */
interface IERC2981 is IERC165 {
    /**
     * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
     * exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
     */
    function royaltyInfo(
        uint256 tokenId,
        uint256 salePrice
    ) external view returns (address receiver, uint256 royaltyAmount);
}

/**
 * @dev Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information.
 *
 * Royalty information can be specified globally for all token ids via {_setDefaultRoyalty}, and/or individually for
 * specific token ids via {_setTokenRoyalty}. The latter takes precedence over the first.
 *
 * Royalty is specified as a fraction of sale price. {_feeDenominator} is overridable but defaults to 10000, meaning the
 * fee is specified in basis points by default.
 *
 * IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See
 * https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the EIP. Marketplaces are expected to
 * voluntarily pay royalties together with sales, but note that this standard is not yet widely supported.
 *
 * _Available since v4.5._
 */
abstract contract ERC2981 is IERC2981, ERC165 {
    struct RoyaltyInfo {
        address receiver;
        uint96 royaltyFraction;
    }

    RoyaltyInfo private _defaultRoyaltyInfo;
    mapping(uint256 => RoyaltyInfo) private _tokenRoyaltyInfo;

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(
        bytes4 interfaceId
    ) public view virtual override(IERC165, ERC165) returns (bool) {
        return
            interfaceId == type(IERC2981).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @inheritdoc IERC2981
     */
    function royaltyInfo(
        uint256 tokenId,
        uint256 salePrice
    ) public view virtual override returns (address, uint256) {
        RoyaltyInfo memory royalty = _tokenRoyaltyInfo[tokenId];

        if (royalty.receiver == address(0)) {
            royalty = _defaultRoyaltyInfo;
        }

        uint256 royaltyAmount = (salePrice * royalty.royaltyFraction) /
            _feeDenominator();

        return (royalty.receiver, royaltyAmount);
    }

    /**
     * @dev The denominator with which to interpret the fee set in {_setTokenRoyalty} and {_setDefaultRoyalty} as a
     * fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an
     * override.
     */
    function _feeDenominator() internal pure virtual returns (uint96) {
        return 10000;
    }

    /**
     * @dev Sets the royalty information that all ids in this contract will default to.
     *
     * Requirements:
     *
     * - `receiver` cannot be the zero address.
     * - `feeNumerator` cannot be greater than the fee denominator.
     */
    function _setDefaultRoyalty(
        address receiver,
        uint96 feeNumerator
    ) internal virtual {
        require(
            feeNumerator <= _feeDenominator(),
            "ERC2981: royalty fee will exceed salePrice"
        );
        require(receiver != address(0), "ERC2981: invalid receiver");

        _defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator);
    }

    /**
     * @dev Removes default royalty information.
     */
    function _deleteDefaultRoyalty() internal virtual {
        delete _defaultRoyaltyInfo;
    }

    /**
     * @dev Sets the royalty information for a specific token id, overriding the global default.
     *
     * Requirements:
     *
     * - `receiver` cannot be the zero address.
     * - `feeNumerator` cannot be greater than the fee denominator.
     */
    function _setTokenRoyalty(
        uint256 tokenId,
        address receiver,
        uint96 feeNumerator
    ) internal virtual {
        require(
            feeNumerator <= _feeDenominator(),
            "ERC2981: royalty fee will exceed salePrice"
        );
        require(receiver != address(0), "ERC2981: Invalid parameters");

        _tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator);
    }

    /**
     * @dev Resets royalty information for the token id back to the global default.
     */
    function _resetTokenRoyalty(uint256 tokenId) internal virtual {
        delete _tokenRoyaltyInfo[tokenId];
    }
}

pragma solidity ^0.8.13;

interface ApeForgeInterface {
    function _nftTokenForges(uint256 tokenId) external view returns (address);
}

contract AzukiApes is ERC721A, Ownable, OperatorFilterer {
    bool public operatorFilteringEnabled;

    address private ApeContract = 0x813b5c4aE6b188F4581aa1dfdB7f4Aba44AA578B;
    address private ApeForge = 0xc4Df0F70A590D01e6cA3B15284b2001c0c60c695;

    uint256 private maxSupply = 3333;
    bool public claimIsActive = true;

    string private _baseTokenURI =
        "ipfs://QmYfNQVevkhXYoV9Aoo4QuAYpenWrGQCk2AUAuGyh3aLUk/";

    // NOTE: Map old token id to new (same) token id
    mapping(uint => uint256) public apeClaimed;
    mapping(uint256 => bool) private _lockedTokens;

    mapping(uint256 => uint256) private _lockTimestamps;

    constructor() ERC721A("Azuki Apes", "AZKI") {
        _registerForOperatorFiltering();
        operatorFilteringEnabled = true;
    }

    function isClaimed(uint256 tokenId) external view returns (uint256) {
        return apeClaimed[tokenId];
    }

    function burn(uint256 tokenId) external {
        require(
            msg.sender == ownerOf(tokenId) || msg.sender == owner(),
            "Must own or be contract owner to burn"
        );
        _burn(tokenId);
    }

    event ClaimActivation(bool isActive);
    event ApeWasClaimed(address theAddress, uint256 theTokenId);
    event ApeNotFound(address theAddress, uint256 theTokenId, uint256 isForged);

    function mintApe(uint256 tokenId) internal {
        _safeMint(msg.sender, tokenId);
        apeClaimed[tokenId] = 1;
        emit ApeWasClaimed(msg.sender, tokenId);
    }

    function claimApes(uint256[] memory theTokenIDs) public {
        require(claimIsActive, "Claim Inactive");

        uint256 numTokens = theTokenIDs.length;
        require(numTokens != 0, "Empty token ID array");

        // Fetch the balance once instead of in every loop iteration
        uint256 senderBalance = IERC721(ApeContract).balanceOf(msg.sender);
        require(senderBalance >= numTokens, "Insufficient NFT balance");

        for (uint256 i; i < numTokens; i++) {
            uint256 tokenId = theTokenIDs[i];

            if (tokenId > maxSupply) {
                continue;
            }

            if (apeClaimed[tokenId] == 0) {
                if (IERC721(ApeContract).ownerOf(tokenId) != msg.sender) {
                    emit ApeNotFound(msg.sender, tokenId, 0);
                    continue;
                }
                mintApe(tokenId);
            }
        }
    }

    function claimForgedApes(uint256[] memory theTokenIDs) external {
        require(claimIsActive, "Claim Inactive");

        uint256 numTokens = theTokenIDs.length;
        require(numTokens != 0, "Empty token ID array");

        for (uint256 i; i < numTokens; i++) {
            uint256 tokenId = theTokenIDs[i];

            if (tokenId > maxSupply) {
                continue;
            }

            if (apeClaimed[tokenId] == 0) {
                if (
                    ApeForgeInterface(ApeForge)._nftTokenForges(tokenId) !=
                    msg.sender
                ) {
                    emit ApeNotFound(msg.sender, tokenId, 1);
                    continue;
                }
                mintApe(tokenId);
            }
        }
    }

    function getWalletOfOwner(
        address owner
    ) external view returns (uint256[] memory) {
        unchecked {
            uint256[] memory a = new uint256[](balanceOf(owner));
            uint256 end = _currentIndex;
            uint256 tokenIdsIdx;
            address currOwnershipAddr;
            for (uint256 i; i < end; i++) {
                TokenOwnership memory ownership = _ownerships[i];
                if (ownership.burned) {
                    continue;
                }
                if (ownership.addr != address(0)) {
                    currOwnershipAddr = ownership.addr;
                }
                if (currOwnershipAddr == owner) {
                    a[tokenIdsIdx++] = i;
                }
            }
            return a;
        }
    }

    // ------------------------------------------------------------
    // Token Locking, Locking info, and other calculations
    // ------------------------------------------------------------

    function lockToken(uint256 tokenId) external {
        require(tokenId > 0, "Token ID must be 1 or greater");

        // Make sure the caller is the owner of the NFT
        require(ownerOf(tokenId) == msg.sender, "Not the owner of the NFT");
        _lockedTokens[tokenId] = true;
        _lockTimestamps[tokenId] = block.timestamp;
    }

    function getDaysLocked(uint256 tokenId) external view returns (uint256) {
        require(_lockedTokens[tokenId], "Token is not locked");
        uint256 lockTimestamp = _lockTimestamps[tokenId];
        uint256 daysLocked = (block.timestamp - lockTimestamp) / 1 days;
        return daysLocked;
    }

    function unlockToken(uint256 tokenId) external {
        require(tokenId > 0, "Token ID must be 1 or greater");

        // Make sure the caller is the owner of the NFT
        require(ownerOf(tokenId) == msg.sender, "Not the owner of the NFT");
        _lockedTokens[tokenId] = false;

        // NOTE: Should we reset the timestamp here?
        _lockTimestamps[tokenId] = block.timestamp;
    }

    function isTokenLocked(uint256 tokenId) public view returns (bool) {
        return _lockedTokens[tokenId];
    }

    // --------------------------------------------------------------------
    // OpenSea's new requirements to make sure royalties work (for now,
    // until they miraculously again decide to rug all of web3 nfts again)
    // They are also bloated as fuck and generally annoying, but if they do
    // not exist, OpenSea refuses royalties and transfers are broken. 🖕
    //
    // ref: https://github.com/Vectorized/closedsea/tree/main
    function setApprovalForAll(
        address operator,
        bool approved
    ) public override(ERC721A) onlyAllowedOperatorApproval(operator) {
        super.setApprovalForAll(operator, approved);
    }

    function approve(
        address operator,
        uint256 tokenId
    ) public override(ERC721A) onlyAllowedOperatorApproval(operator) {
        super.approve(operator, tokenId);
    }