// SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; /** * @title DramaNFT * @dev This contract is used to create and manage drama series NFTs */ contract IPNFT is ERC721URIStorage, Ownable, AccessControl { // Token ID counter uint256 private _nextTokenId; // Minter role bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); // Media series NFT information struct MediaInfo { string seriesTitle; // Series title string description; // Series description uint256 totalSeasons; // Total seasons count uint256 totalEpisodes; // Total episodes count string[] genres; // Genre tags string[] creators; // Creators list uint256 createdAt; // Creation timestamp string posterUri; // Poster URI } // Mapping from token ID to media info mapping(uint256 => MediaInfo) private _mediaInfo; // Events event MediaNFTMinted( uint256 indexed tokenId, address indexed owner, string seriesTitle ); constructor( address initialOwner ) ERC721("Media Series NFT", "MEDIA") Ownable(initialOwner) { _grantRole(DEFAULT_ADMIN_ROLE, initialOwner); _grantRole(MINTER_ROLE, initialOwner); } /** * @dev Adds a new minter * @param account The address to grant minter role */ function addMinter(address account) public onlyOwner { _grantRole(MINTER_ROLE, account); } /** * @dev Removes a minter * @param account The address to revoke minter role */ function removeMinter(address account) public onlyOwner { _revokeRole(MINTER_ROLE, account); } /** * @dev Checks if an account is a minter * @param account The address to check */ function isMinter(address account) public view returns (bool) { return hasRole(MINTER_ROLE, account); } /** * @dev Mints a new media series NFT * @param to The receiver address of the token * @param uri The token metadata URI * @return The newly minted token ID */ function mint(address to, string memory uri) public returns (uint256) { require( hasRole(MINTER_ROLE, msg.sender) || msg.sender == owner(), "MediaNFT: caller is not a minter or owner" ); uint256 tokenId = _nextTokenId; _nextTokenId++; _safeMint(to, tokenId); _setTokenURI(tokenId, uri); emit MediaNFTMinted(tokenId, to, ""); return tokenId; } /** * @dev Gets the media series information * @param tokenId Token ID */ function getMediaInfo( uint256 tokenId ) public view returns (MediaInfo memory) { require(_exists(tokenId), "MediaNFT: token does not exist"); return _mediaInfo[tokenId]; } /** * @dev Sets the media series information * @param tokenId Token ID * @param seriesTitle Series title * @param description Series description * @param totalSeasons Total seasons count * @param totalEpisodes Total episodes count * @param genres Genre tags * @param creators Creators list * @param posterUri Poster URI */ function setMediaInfo( uint256 tokenId, string memory seriesTitle, string memory description, uint256 totalSeasons, uint256 totalEpisodes, string[] memory genres, string[] memory creators, string memory posterUri ) public { require( hasRole(MINTER_ROLE, msg.sender) || msg.sender == owner(), "MediaNFT: caller is not a minter or owner" ); require(_exists(tokenId), "MediaNFT: token does not exist"); MediaInfo storage media = _mediaInfo[tokenId]; media.seriesTitle = seriesTitle; media.description = description; media.totalSeasons = totalSeasons; media.totalEpisodes = totalEpisodes; media.genres = genres; media.creators = creators; media.posterUri = posterUri; media.createdAt = block.timestamp; } /** * @dev Gets the total number of minted NFTs */ function totalSupply() public view returns (uint256) { return _nextTokenId; } /** * @dev Gets all tokens owned by a specific address * @param owner The owner address */ function tokensOfOwner( address owner ) public view returns (uint256[] memory) { uint256 tokenCount = balanceOf(owner); uint256[] memory tokenIds = new uint256[](tokenCount); for (uint256 i = 0; i < tokenCount; i++) { tokenIds[i] = tokenOfOwnerByIndex(owner, i); } return tokenIds; } /** * @dev Returns the token ID at a given index of an owner's token list * @param owner The owner address * @param index The index in the owner's token list */ function tokenOfOwnerByIndex( address owner, uint256 index ) public view returns (uint256) { require(index < balanceOf(owner), "MediaNFT: index out of bounds"); uint256 count = 0; for (uint256 i = 0; i < _nextTokenId; i++) { if (_exists(i) && ownerOf(i) == owner) { if (count == index) { return i; } count++; } } revert("MediaNFT: index out of bounds"); } /** * @dev Checks if a token exists */ function _exists(uint256 tokenId) internal view returns (bool) { return _ownerOf(tokenId) != address(0); } // Override _update to add custom logic when transferring NFTs function _update( address to, uint256 tokenId, address auth ) internal override returns (address) { address from = super._update(to, tokenId, auth); // Add any custom transfer logic here if needed return from; } // Override supportsInterface to support both ERC721 and AccessControl function supportsInterface( bytes4 interfaceId ) public view virtual override(ERC721URIStorage, AccessControl) returns (bool) { return super.supportsInterface(interfaceId); } }