Files
Goa-gel-fullstack/blockchain/contracts/LicenseNFT.sol
Mahi 80566bf0a2 feat: Goa GEL Blockchain e-Licensing Platform - Full Stack Implementation
Complete implementation of the Goa Government e-Licensing platform with:

Backend:
- NestJS API with JWT authentication
- PostgreSQL database with Knex ORM
- Redis caching and session management
- MinIO document storage
- Hyperledger Besu blockchain integration
- Multi-department workflow system
- Comprehensive API tests (266/282 passing)

Frontend:
- Angular 21 with standalone components
- Angular Material + TailwindCSS UI
- Visual workflow builder
- Document upload with progress tracking
- Blockchain explorer integration
- Role-based dashboards (Admin, Department, Citizen)
- E2E tests with Playwright (37 tests)

Infrastructure:
- Docker Compose orchestration
- Blockscout blockchain explorer
- Development and production configurations
2026-02-07 10:23:29 -04:00

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);
}
}