// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; /** * @title LicenseNFT * @notice ERC721 token representing government-issued licenses * @dev Each license is minted as an NFT with associated metadata */ contract LicenseNFT is ERC721, ERC721URIStorage, Ownable { uint256 private _nextTokenId; // Mapping from request ID to token ID mapping(string => uint256) private _requestToToken; // Mapping from token ID to request ID mapping(uint256 => string) private _tokenToRequest; // Mapping to track revoked licenses mapping(uint256 => bool) private _revokedTokens; // Mapping to store revocation reasons mapping(uint256 => string) private _revocationReasons; // Mapping to store license metadata URI mapping(uint256 => string) private _metadataUris; // Events event LicenseMinted( uint256 indexed tokenId, address indexed to, string requestId, string metadataUri ); event LicenseRevoked( uint256 indexed tokenId, string reason ); constructor() ERC721("Goa Government License", "GOA-LIC") Ownable(msg.sender) {} /** * @notice Mint a new license NFT * @param to The address to mint the token to * @param requestId The associated license request ID * @param metadataUri The URI containing license metadata * @return tokenId The ID of the newly minted token */ function mint( address to, string calldata requestId, string calldata metadataUri ) public onlyOwner returns (uint256) { require(bytes(requestId).length > 0, "Request ID required"); require(_requestToToken[requestId] == 0, "License already minted for this request"); uint256 tokenId = ++_nextTokenId; _safeMint(to, tokenId); _setTokenURI(tokenId, metadataUri); _requestToToken[requestId] = tokenId; _tokenToRequest[tokenId] = requestId; _metadataUris[tokenId] = metadataUri; emit LicenseMinted(tokenId, to, requestId, metadataUri); return tokenId; } /** * @notice Get the token ID for a request * @param requestId The license request ID * @return The token ID (0 if not found) */ function tokenOfRequest(string calldata requestId) public view returns (uint256) { return _requestToToken[requestId]; } /** * @notice Get the request ID for a token * @param tokenId The token ID * @return The request ID */ function requestOfToken(uint256 tokenId) public view returns (string memory) { require(_ownerOf(tokenId) != address(0), "Token does not exist"); return _tokenToRequest[tokenId]; } /** * @notice Check if a token exists * @param tokenId The token ID to check * @return True if the token exists */ function exists(uint256 tokenId) public view returns (bool) { return _ownerOf(tokenId) != address(0); } /** * @notice Revoke a license * @param tokenId The token ID to revoke * @param reason The reason for revocation */ function revoke(uint256 tokenId, string calldata reason) public onlyOwner { require(_ownerOf(tokenId) != address(0), "Token does not exist"); require(!_revokedTokens[tokenId], "License already revoked"); _revokedTokens[tokenId] = true; _revocationReasons[tokenId] = reason; emit LicenseRevoked(tokenId, reason); } /** * @notice Check if a license is revoked * @param tokenId The token ID to check * @return True if the license is revoked */ function isRevoked(uint256 tokenId) public view returns (bool) { return _revokedTokens[tokenId]; } /** * @notice Get the revocation reason for a license * @param tokenId The token ID * @return The revocation reason */ function getRevocationReason(uint256 tokenId) public view returns (string memory) { return _revocationReasons[tokenId]; } /** * @notice Get the metadata URI for a token * @param tokenId The token ID * @return The metadata URI */ function getMetadata(uint256 tokenId) public view returns (string memory) { require(_ownerOf(tokenId) != address(0), "Token does not exist"); return _metadataUris[tokenId]; } /** * @notice Get the total number of minted licenses * @return The total count */ function totalSupply() public view returns (uint256) { return _nextTokenId; } // Override required functions function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { return super.tokenURI(tokenId); } function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) { return super.supportsInterface(interfaceId); } }