Contract code
Overview
This is the complete source code for the IliadDCA Digital Certificate of Authenticity contract. It is open-source so that engineers and counsel can inspect and audit the exact behavior of the system line by line.
Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
/**
* @title IliadDCA
* @notice ERC721 Digital Certificate of Authenticity (DCA) contract with primary and backup URIs and failover capabilities.
*/
contract IliadDCA is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
uint256 public switchDate;
string private primaryBaseURI;
string private backupBaseURI;
uint256 private _tokenIdCounter;
mapping(uint256 => string) private _tokenPath;
mapping(uint256 => bool) private _tokenLocked;
string private baseContractURI;
string private backupContractURI;
// Events
event Minted(address indexed to, uint256 indexed tokenId, string tokenPath);
event BaseURISet(string newBaseURI);
event BackupURISet(string newBackupURI);
event ContractURISet(string newURI);
event BackupContractURISet(string newBackupURI);
event SwitchDateUpdated(uint256 newSwitchDate);
event TokenBurned(uint256 indexed tokenId);
event Locked(uint256 tokenId);
event Unlocked(uint256 tokenId);
/**
* @notice Initializes contract with primary and backup URIs, sets deployer as owner, and sets an initial switch date.
* @param initialBaseURI Initial primary base URI.
* @param initialBackupBaseURI Initial backup base URI.
* @param initialSwitchDate Timestamp when failover to backup URI becomes active.
*/
function initialize(string memory initialBaseURI, string memory initialBackupBaseURI, uint256 initialSwitchDate) public initializer {
__ERC721_init("ili.ad DCA", "IDCA");
__Ownable_init(msg.sender);
__UUPSUpgradeable_init();
primaryBaseURI = initialBaseURI;
backupBaseURI = initialBackupBaseURI;
switchDate = initialSwitchDate;
_tokenIdCounter = 0;
emit BaseURISet(initialBaseURI);
emit BackupURISet(initialBackupBaseURI);
emit SwitchDateUpdated(initialSwitchDate);
}
/**
* @notice Mint a new token with a dynamic URI path.
* @param to Recipient address of the DCA.
* @param tokenPath Unique path appended to the active base URI.
*/
function mint(address to, string memory tokenPath) external onlyOwner {
uint256 tokenId = ++_tokenIdCounter;
_safeMint(to, tokenId);
_tokenPath[tokenId] = tokenPath;
_tokenLocked[tokenId] = false;
emit Minted(to, tokenId, tokenPath);
}
/**
* @notice Lock token to disable independent NFT trading (only callable by contract owner).
* This explicitly prevents token transfers by token owner until explicitly unlocked.
* @param tokenId Token ID to lock.
*/
function lockToken(uint256 tokenId) external onlyOwner {
require(ownerOf(tokenId) != address(0), "Lock nonexistent token");
_tokenLocked[tokenId] = true;
emit Locked(tokenId);
}
/**
* @notice Unlock token to re-enable NFT trading (only callable by contract owner).
* This explicitly allows token owner to transfer token again.
* @param tokenId Token ID to unlock.
*/
function unlockToken(uint256 tokenId) external onlyOwner {
require(ownerOf(tokenId) != address(0), "Unlock nonexistent token");
_tokenLocked[tokenId] = false;
emit Unlocked(tokenId);
}
/**
* @notice Check if a specific token is currently locked.
* @param tokenId Token ID to check lock status.
* @return Boolean indicating locked (true) or unlocked (false) state.
*/
function isLocked(uint256 tokenId) external view returns (bool) {
return _tokenLocked[tokenId];
}
/**
* @notice Override internal update hook to enforce token lock.
* This prevents transfers if the token is explicitly locked.
*/
function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
require(!_tokenLocked[tokenId], "Token is locked");
return super._update(to, tokenId, auth);
}
/**
* @notice Burn an existing token.
* @param tokenId The ID of the token to burn.
*/
function burn(uint256 tokenId) external onlyOwner {
require(ownerOf(tokenId) != address(0), "Cannot burn nonexistent token");
_burn(tokenId);
delete _tokenPath[tokenId];
emit TokenBurned(tokenId);
}
/**
* @notice Update the primary base URI.
* @param newPrimaryBaseURI The new primary base URI to use.
*/
function setPrimaryBaseURI(string memory newPrimaryBaseURI) external onlyOwner {
primaryBaseURI = newPrimaryBaseURI;
emit BaseURISet(newPrimaryBaseURI);
}
/**
* @notice Update the backup base URI.
* @param newBackupBaseURI The new backup base URI to use.
*/
function setBackupBaseURI(string memory newBackupBaseURI) external onlyOwner {
backupBaseURI = newBackupBaseURI;
emit BackupURISet(newBackupBaseURI);
}
/**
* @notice Update the failover switch date.
* @param newSwitchDate The new timestamp after which the backup URI becomes active.
*/
function setSwitchDate(uint256 newSwitchDate) external onlyOwner {
require(newSwitchDate > block.timestamp, "Switch date must be in the future");
switchDate = newSwitchDate;
emit SwitchDateUpdated(newSwitchDate);
}
/**
* @notice Retrieves the current primary base URI.
* @return The primary base URI as a string.
*/
function getPrimaryBaseURI() external view returns (string memory) {
return primaryBaseURI;
}
/**
* @notice Retrieves the current backup base URI.
* @return The backup base URI as a string.
*/
function getBackupBaseURI() external view returns (string memory) {
return backupBaseURI;
}
/**
* @notice Retrieves the active contract-level metadata URI.
* @dev Returns primary contract metadata URI if set; otherwise, returns backup URI.
* @return A string representing the active contract metadata URI.
*/
function contractURI() public view returns (string memory) {
if (bytes(baseContractURI).length > 0) {
return baseContractURI;
} else {
return backupContractURI;
}
}
/**
* @notice Sets or updates the primary contract-level metadata URI.
* @dev Only callable by the contract owner.
* @param newURI The new primary contract metadata URI.
*/
function setContractURI(string calldata newURI) external onlyOwner {
baseContractURI = newURI;
emit ContractURISet(newURI);
}
/**
* @notice Sets or updates the backup contract-level metadata URI.
* @dev Backup URI is used as a fallback when primary URI is unset or empty. Only callable by the contract owner.
* @param newBackupURI The new backup contract metadata URI.
*/
function setBackupContractURI(string calldata newBackupURI) external onlyOwner {
backupContractURI = newBackupURI;
emit BackupContractURISet(newBackupURI);
}
/**
* @notice Returns the token URI dynamically assembled based on the current timestamp and switch date.
* @param tokenId The ID of the token whose URI is requested.
* @return The dynamically generated token URI.
*/
function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(ownerOf(tokenId) != address(0), "URI query for nonexistent token");
string memory activeBaseURI = block.timestamp > switchDate ? backupBaseURI : primaryBaseURI;
return string(abi.encodePacked(activeBaseURI, _tokenPath[tokenId]));
}
/**
* @notice Authorizes the contract upgrade.
* @param newImplementation Address of the new implementation.
*/
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
// Storage gap for future-proofing upgrades
uint256[47] private __gap;
}