DreamEssenceAR.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; // Keep non-upgradeable for interface
import "./interfaces/IDreamNFTExtension.sol";

/**
 * @title DreamNFTExtensionAR
 * @dev Upgradeable extension for DreamNFT providing AR metadata features.
 */
contract DreamNFTExtensionAR is 
    Initializable, 
    IDreamNFTExtension, 
    OwnableUpgradeable, 
    UUPSUpgradeable,
    ERC165Upgradeable
{
    // Address of the main DreamNFT contract this extension is linked to
    address public dreamNFTAddress; 
    
    struct ARMetadata {
        bool hasHoloEffect;
        string arUri;
        string holoType;
        uint256 unlockTime;
    }
    
    mapping(uint256 => ARMetadata) public arMetadata;
    mapping(uint256 => bool) public arUnlocked;
    
    event ARUnlocked(uint256 indexed tokenId, string arUri);
    event HoloEffectAdded(uint256 indexed tokenId, string holoType);

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /**
     * @dev Initializes the contract, linking it to the main DreamNFT contract.
     * Replaces the constructor in the upgradeable pattern.
     * @param _mainNFTAddress The address of the DreamNFT contract.
     */
    function initialize(address _mainNFTAddress) public override initializer {
        require(_mainNFTAddress != address(0), "AR: Zero address");
        __Ownable_init(msg.sender); // Initialize Ownable
         // Ensure owner is correctly set.
        _transferOwnership(_msgSender());
        __UUPSUpgradeable_init(); // Initialize UUPS
        __ERC165_init(); // Initialize ERC165

        dreamNFTAddress = _mainNFTAddress; // Set the linked NFT contract
    }

    /**
     * @dev Authorizes contract upgrade through the UUPS proxy pattern.
     * Only callable by the owner.
     */
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

    /**
     * @dev Sets AR metadata for a specific DreamNFT token. Only owner can call.
     * @param tokenId The ID of the DreamNFT token.
     * @param arUri The URI for the AR experience.
     * @param holoType The type descriptor for the holo effect.
     */
    function setARMetadata(
        uint256 tokenId,
        string memory arUri,
        string memory holoType
    ) external onlyOwner {
        require(_exists(tokenId), "Token does not exist");
        
        arMetadata[tokenId] = ARMetadata({
            hasHoloEffect: true,
            arUri: arUri,
            holoType: holoType,
            unlockTime: block.timestamp
        });
        
        emit HoloEffectAdded(tokenId, holoType);
    }
    
    function unlockAR(uint256 tokenId) external {
        require(_exists(tokenId), "AR: Token does not exist");
        require(IERC721(dreamNFTAddress).ownerOf(tokenId) == msg.sender, "AR: Not token owner");
        require(!arUnlocked[tokenId], "AR: Already unlocked");

        arUnlocked[tokenId] = true;
        emit ARUnlocked(tokenId, arMetadata[tokenId].arUri);
    }
    
    function _exists(uint256 tokenId) internal view returns (bool) {
        // Modified implementation to ensure proper error propagation
        try IERC721(dreamNFTAddress).ownerOf(tokenId) returns (address) {
            return true;
        } catch Error(string memory) {
            return false;
        } catch {
            return false;
        }
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     * Includes support for IDreamNFTExtension and standard ERC165.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(IDreamNFTExtension, ERC165Upgradeable) returns (bool) {
        // Explicitly check the interfaces this contract and its relevant bases support
        return interfaceId == type(IDreamNFTExtension).interfaceId || 
               super.supportsInterface(interfaceId);
    }
}