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
This commit is contained in:
172
blockchain/contracts/LicenseNFT.sol
Normal file
172
blockchain/contracts/LicenseNFT.sol
Normal file
@@ -0,0 +1,172 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user