FragmentOfLucidity.sol

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

import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/common/ERC2981Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

interface IFragmentOfLucidity {
    function isRedeemed(uint256 tokenId) external view returns (bool);
    function markRedeemed(uint256 tokenId) external;
    function totalMinted() external view returns (uint256);
}

/**
 * @title FragmentOfLucidity
 * @notice ERC721 contract representing Fragments of Lucidity.
 *         Each fragment can be used (marked as redeemed) in the upgrade process.
 *         Implements ERC2981 to enforce a 6% royalty on secondary sales.
 *         Now uses the UUPS upgradeability pattern.
 */
contract FragmentOfLucidity is 
    Initializable,
    ERC721Upgradeable, 
    OwnableUpgradeable, 
    ERC2981Upgradeable, 
    UUPSUpgradeable,
    IFragmentOfLucidity 
{
    uint256 public constant MAX_FRAGMENT_SUPPLY = 2500;
    uint256 public totalFragmentsMinted;
    bool public ownerFragmentsMinted; // Tracks one-time mint of 50 fragments to owner

    mapping(uint256 => bool) private _redeemed;

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

    /**
     * @notice Initializes the contract replacing the constructor
     * @param initialOwner The initial owner of the contract who gets royalties
     */
    function initialize(address initialOwner) public initializer {
        __ERC721_init("Fragment of Lucidity", "FOL");
        __Ownable_init(initialOwner);
        __ERC2981_init();
        __UUPSUpgradeable_init();
        
        // Set default royalty to 6% (600 basis points) for the owner
        _setDefaultRoyalty(initialOwner, 600);
    }

    /**
     * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract.
     * Called by {upgradeTo} and {upgradeToAndCall}.
     */
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

    /**
     * @notice Mints a single Fragment to a specified address.
     * @dev Only owner can mint. Ensures total supply does not exceed 2,500.
     */
    function mintFragment(address to) external onlyOwner {
        require(totalFragmentsMinted < MAX_FRAGMENT_SUPPLY, "FRAG: Max supply reached");
        totalFragmentsMinted += 1;
        uint256 newId = totalFragmentsMinted;
        _safeMint(to, newId);
    }

    /**
     * @notice Mints 50 Fragments for giveaways or influencers. Can only be called once.
     */
    function mintOwnerFragmentsOnce() external onlyOwner {
        require(!ownerFragmentsMinted, "FRAG: Already minted 50 to owner");
        // Mint 50 to contract owner
        for (uint256 i = 0; i < 50; i++) {
            require(totalFragmentsMinted < MAX_FRAGMENT_SUPPLY, "FRAG: Max supply reached");
            totalFragmentsMinted++;
            _safeMint(owner(), totalFragmentsMinted);
        }
        ownerFragmentsMinted = true;
    }

    /**
     * @notice Checks if a fragment has been redeemed for an upgrade.
     */
    function isRedeemed(uint256 tokenId) external view override returns (bool) {
        return _redeemed[tokenId];
    }

    /**
     * @notice Marks a fragment as redeemed without burning it.
     */
    function markRedeemed(uint256 tokenId) external override onlyOwner {
        require(tokenId <= totalFragmentsMinted, "FRAG: Nonexistent fragment");
        require(!_redeemed[tokenId], "FRAG: Already redeemed");
        _redeemed[tokenId] = true;
    }

    /**
     * @notice Returns the total minted supply (including redeemed).
     */
    function totalMinted() external view override returns (uint256) {
        return totalFragmentsMinted;
    }

    /**
     * @notice Override supportsInterface to include ERC2981 interface.
     */
    function supportsInterface(bytes4 interfaceId) 
        public 
        view 
        virtual 
        override(ERC721Upgradeable, ERC2981Upgradeable) 
        returns (bool) 
    {
        return super.supportsInterface(interfaceId);
    }
}