173 lines
5.1 KiB
Solidity
173 lines
5.1 KiB
Solidity
|
|
// 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);
|
||
|
|
}
|
||
|
|
}
|