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.

View Contract on GitHub

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

Was this page helpful?