Description:
Smart contract deployed on Ethereum with Factory features.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/LicenseAnchor.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/**
* @title LicenseAnchor
* @dev Anchors licenses on the blockchain for immutable verification
* @notice Supports both regular and enterprise licenses from Shuka
*/
contract LicenseAnchor {
// Admin address (Shuka's admin wallet)
address public admin;
// License types
enum LicenseType { REGULAR, ENTERPRISE }
// License status
enum LicenseStatus { ACTIVE, EXPIRED, REVOKED, TRANSFERRED }
// License structure matching Shuka's License model
struct License {
string licenseId; // Unique license ID (e.g., LIC-20240814-123456-abc)
LicenseType licenseType; // Regular or Enterprise
string brand; // Brand name
string rightsHolder; // Rights holder name
address issuerWallet; // Wallet that issued the license
address ownerWallet; // Current owner's wallet
uint256 issueDate; // Issue timestamp
uint256 validUntil; // Expiration timestamp
uint256 buyToOpenPrice; // Price in wei
bool approved; // Approval status
bool available; // Availability for purchase
LicenseStatus status; // Current status
string metadataHash; // IPFS hash for additional metadata
uint256 anchorTimestamp; // When anchored on blockchain
}
// Enterprise-specific data
struct EnterpriseMetadata {
uint256 teamId; // Team ID from database
uint256 monthlyLimit; // Monthly license limit
bool allowCreation; // Allow license creation
bool allowManagement; // Allow enterprise management
}
// Mappings
mapping(string => License) public licenses;
mapping(string => EnterpriseMetadata) public enterpriseMetadata;
mapping(address => string[]) public userLicenses;
mapping(address => string[]) public issuedLicenses;
mapping(string => bool) public licenseExists;
// Transfer history
mapping(string => address[]) public transferHistory;
// Events
event LicenseAnchored(
string indexed licenseId,
LicenseType indexed licenseType,
address indexed ownerWallet,
uint256 timestamp
);
event LicenseTransferred(
string indexed licenseId,
address indexed from,
address indexed to,
uint256 timestamp
);
event LicenseStatusChanged(
string indexed licenseId,
LicenseStatus indexed oldStatus,
LicenseStatus indexed newStatus,
uint256 timestamp
);
event LicensePurchased(
string indexed licenseId,
address indexed buyer,
uint256 price,
uint256 timestamp
);
// Modifiers
modifier onlyAdmin() {
require(msg.sender == admin, "Only admin can perform this action");
_;
}
modifier licenseValid(string memory _licenseId) {
require(licenseExists[_licenseId], "License does not exist");
_;
}
modifier onlyLicenseOwner(string memory _licenseId) {
require(licenses[_licenseId].ownerWallet == msg.sender, "Not the license owner");
_;
}
constructor() {
admin = msg.sender;
}
/**
* @dev Anchor a new license on the blockchain
* @param _licenseId Unique license identifier
* @param _licenseType Type of license (0 for Regular, 1 for Enterprise)
* @param _brand Brand name
* @param _rightsHolder Rights holder name
* @param _ownerWallet Initial owner's wallet address
* @param _validUntil Expiration timestamp
* @param _buyToOpenPrice Price in wei
* @param _metadataHash IPFS hash for additional metadata
*/
function anchorLicense(
string memory _licenseId,
LicenseType _licenseType,
string memory _brand,
string memory _rightsHolder,
address _ownerWallet,
uint256 _validUntil,
uint256 _buyToOpenPrice,
string memory _metadataHash
) external onlyAdmin {
require(!licenseExists[_licenseId], "License already anchored");
require(_ownerWallet != address(0), "Invalid owner wallet");
require(_validUntil > block.timestamp, "Expiration must be in future");
License memory newLicense = License({
licenseId: _licenseId,
licenseType: _licenseType,
brand: _brand,
rightsHolder: _rightsHolder,
issuerWallet: msg.sender,
ownerWallet: _ownerWallet,
issueDate: block.timestamp,
validUntil: _validUntil,
buyToOpenPrice: _buyToOpenPrice,
approved: true,
available: true,
status: LicenseStatus.ACTIVE,
metadataHash: _metadataHash,
anchorTimestamp: block.timestamp
});
licenses[_licenseId] = newLicense;
licenseExists[_licenseId] = true;
userLicenses[_ownerWallet].push(_licenseId);
issuedLicenses[msg.sender].push(_licenseId);
transferHistory[_licenseId].push(_ownerWallet);
emit LicenseAnchored(_licenseId, _licenseType, _ownerWallet, block.timestamp);
}
/**
* @dev Add enterprise metadata for enterprise licenses
* @param _licenseId License identifier
* @param _teamId Team ID from database
* @param _monthlyLimit Monthly license creation limit
* @param _allowCreation Allow license creation
* @param _allowManagement Allow enterprise management
*/
function setEnterpriseMetadata(
string memory _licenseId,
uint256 _teamId,
uint256 _monthlyLimit,
bool _allowCreation,
bool _allowManagement
) external onlyAdmin licenseValid(_licenseId) {
require(licenses[_licenseId].licenseType == LicenseType.ENTERPRISE, "Not an enterprise license");
enterpriseMetadata[_licenseId] = EnterpriseMetadata({
teamId: _teamId,
monthlyLimit: _monthlyLimit,
allowCreation: _allowCreation,
allowManagement: _allowManagement
});
}
/**
* @dev Transfer license ownership
* @param _licenseId License to transfer
* @param _newOwner New owner's wallet address
*/
function transferLicense(
string memory _licenseId,
address _newOwner
) external licenseValid(_licenseId) onlyLicenseOwner(_licenseId) {
require(_newOwner != address(0), "Invalid new owner");
require(_newOwner != msg.sender, "Cannot transfer to self");
require(licenses[_licenseId].status == LicenseStatus.ACTIVE, "License not active");
address previousOwner = licenses[_licenseId].ownerWallet;
licenses[_licenseId].ownerWallet = _newOwner;
// Update user license mappings
_removeLicenseFromUser(previousOwner, _licenseId);
userLicenses[_newOwner].push(_licenseId);
transferHistory[_licenseId].push(_newOwner);
emit LicenseTransferred(_licenseId, previousOwner, _newOwner, block.timestamp);
}
/**
* @dev Purchase an available license
* @param _licenseId License to purchase
*/
function purchaseLicense(string memory _licenseId)
external
payable
licenseValid(_licenseId)
{
License storage license = licenses[_licenseId];
require(license.available, "License not available for purchase");
require(license.status == LicenseStatus.ACTIVE, "License not active");
require(msg.value >= license.buyToOpenPrice, "Insufficient payment");
require(msg.sender != license.ownerWallet, "Already own this license");
address previousOwner = license.ownerWallet;
// Transfer ownership
license.ownerWallet = msg.sender;
license.available = false;
// Update mappings
_removeLicenseFromUser(previousOwner, _licenseId);
userLicenses[msg.sender].push(_licenseId);
transferHistory[_licenseId].push(msg.sender);
// Transfer payment to admin
payable(admin).transfer(msg.value);
emit LicensePurchased(_licenseId, msg.sender, msg.value, block.timestamp);
emit LicenseTransferred(_licenseId, previousOwner, msg.sender, block.timestamp);
}
/**
* @dev Update license status
* @param _licenseId License identifier
* @param _newStatus New status
*/
function updateLicenseStatus(
string memory _licenseId,
LicenseStatus _newStatus
) external onlyAdmin licenseValid(_licenseId) {
LicenseStatus oldStatus = licenses[_licenseId].status;
licenses[_licenseId].status = _newStatus;
emit LicenseStatusChanged(_licenseId, oldStatus, _newStatus, block.timestamp);
}
/**
* @dev Check if license is expired
* @param _licenseId License identifier
* @return Boolean indicating if expired
*/
function isLicenseExpired(string memory _licenseId)
external
view
licenseValid(_licenseId)
returns (bool)
{
return block.timestamp > licenses[_licenseId].validUntil;
}
/**
* @dev Get license details
* @param _licenseId License identifier
* @return License struct
*/
function getLicense(string memory _licenseId)
external
view
licenseValid(_licenseId)
returns (License memory)
{
return licenses[_licenseId];
}
/**
* @dev Get all licenses for a user
* @param _user User's wallet address
* @return Array of license IDs
*/
function getUserLicenses(address _user)
external
view
returns (string[] memory)
{
return userLicenses[_user];
}
/**
* @dev Get transfer history for a license
* @param _licenseId License identifier
* @return Array of addresses in transfer history
*/
function getTransferHistory(string memory _licenseId)
external
view
licenseValid(_licenseId)
returns (address[] memory)
{
return transferHistory[_licenseId];
}
/**
* @dev Internal function to remove license from user's array
* @param _user User's address
* @param _licenseId License to remove
*/
function _removeLicenseFromUser(address _user, string memory _licenseId) internal {
string[] storage userLicenseArray = userLicenses[_user];
for (uint i = 0; i < userLicenseArray.length; i++) {
if (keccak256(bytes(userLicenseArray[i])) == keccak256(bytes(_licenseId))) {
userLicenseArray[i] = userLicenseArray[userLicenseArray.length - 1];
userLicenseArray.pop();
break;
}
}
}
/**
* @dev Batch anchor multiple licenses - saves ~60% gas for 10+ licenses
* @param _licenseIds Array of unique license identifiers
* @param _licenseTypes Array of license types
* @param _brands Array of brand names
* @param _rightsHolders Array of rights holder names
* @param _ownerWallets Array of owner wallet addresses
* @param _validUntils Array of expiration timestamps
* @param _prices Array of prices in wei
* @param _metadataHashes Array of IPFS hashes
*/
function batchAnchorLicenses(
string[] memory _licenseIds,
LicenseType[] memory _licenseTypes,
string[] memory _brands,
string[] memory _rightsHolders,
address[] memory _ownerWallets,
uint256[] memory _validUntils,
uint256[] memory _prices,
string[] memory _metadataHashes
) external onlyAdmin {
uint256 length = _licenseIds.length;
require(
_licenseTypes.length == length &&
_brands.length == length &&
_rightsHolders.length == length &&
_ownerWallets.length == length &&
_validUntils.length == length &&
_prices.length == length &&
_metadataHashes.length == length,
"Array length mismatch"
);
uint256 currentTime = block.timestamp;
for (uint256 i = 0; i < length; i++) {
require(!licenseExists[_licenseIds[i]], "License already exists");
require(_ownerWallets[i] != address(0), "Invalid owner");
require(_validUntils[i] > currentTime, "Invalid expiry");
licenses[_licenseIds[i]] = License({
licenseId: _licenseIds[i],
licenseType: _licenseTypes[i],
brand: _brands[i],
rightsHolder: _rightsHolders[i],
issuerWallet: msg.sender,
ownerWallet: _ownerWallets[i],
issueDate: currentTime,
validUntil: _validUntils[i],
buyToOpenPrice: _prices[i],
approved: true,
available: true,
status: LicenseStatus.ACTIVE,
metadataHash: _metadataHashes[i],
anchorTimestamp: currentTime
});
licenseExists[_licenseIds[i]] = true;
userLicenses[_ownerWallets[i]].push(_licenseIds[i]);
issuedLicenses[msg.sender].push(_licenseIds[i]);
transferHistory[_licenseIds[i]].push(_ownerWallets[i]);
emit LicenseAnchored(_licenseIds[i], _licenseTypes[i], _ownerWallets[i], currentTime);
}
}
/**
* @dev Batch update license status - saves ~50% gas for multiple updates
* @param _licenseIds Array of license identifiers
* @param _newStatuses Array of new statuses
*/
function batchUpdateStatus(
string[] memory _licenseIds,
LicenseStatus[] memory _newStatuses
) external onlyAdmin {
require(_licenseIds.length == _newStatuses.length, "Array length mismatch");
for (uint256 i = 0; i < _licenseIds.length; i++) {
require(licenseExists[_licenseIds[i]], "License does not exist");
LicenseStatus oldStatus = licenses[_licenseIds[i]].status;
licenses[_licenseIds[i]].status = _newStatuses[i];
emit LicenseStatusChanged(_licenseIds[i], oldStatus, _newStatuses[i], block.timestamp);
}
}
/**
* @dev Batch set enterprise metadata - saves gas for multiple enterprise licenses
* @param _licenseIds Array of license identifiers
* @param _teamIds Array of team IDs
* @param _monthlyLimits Array of monthly limits
* @param _allowCreations Array of creation permissions
* @param _allowManagements Array of management permissions
*/
function batchSetEnterpriseMetadata(
string[] memory _licenseIds,
uint256[] memory _teamIds,
uint256[] memory _monthlyLimits,
bool[] memory _allowCreations,
bool[] memory _allowManagements
) external onlyAdmin {
uint256 length = _licenseIds.length;
require(
_teamIds.length == length &&
_monthlyLimits.length == length &&
_allowCreations.length == length &&
_allowManagements.length == length,
"Array length mismatch"
);
for (uint256 i = 0; i < length; i++) {
require(licenseExists[_licenseIds[i]], "License does not exist");
require(licenses[_licenseIds[i]].licenseType == LicenseType.ENTERPRISE, "Not enterprise");
enterpriseMetadata[_licenseIds[i]] = EnterpriseMetadata({
teamId: _teamIds[i],
monthlyLimit: _monthlyLimits[i],
allowCreation: _allowCreations[i],
allowManagement: _allowManagements[i]
});
}
}
/**
* @dev Batch check if licenses are expired - view function, no gas cost
* @param _licenseIds Array of license identifiers
* @return Array of booleans indicating expiration status
*/
function batchCheckExpired(
string[] memory _licenseIds
) external view returns (bool[] memory) {
bool[] memory expired = new bool[](_licenseIds.length);
uint256 currentTime = block.timestamp;
for (uint256 i = 0; i < _licenseIds.length; i++) {
if (licenseExists[_licenseIds[i]]) {
expired[i] = currentTime > licenses[_licenseIds[i]].validUntil;
}
}
return expired;
}
/**
* @dev Update admin address
* @param _newAdmin New admin address
*/
function updateAdmin(address _newAdmin) external onlyAdmin {
require(_newAdmin != address(0), "Invalid admin address");
admin = _newAdmin;
}
}"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"viaIR": true,
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}
}}
Submitted on: 2025-09-28 14:52:09
Comments
Log in to comment.
No comments yet.