Skip to content

Instantly share code, notes, and snippets.

@kmjones1979
Created June 10, 2025 21:52
Show Gist options
  • Save kmjones1979/302c41af374bdedac53c67090b547d7f to your computer and use it in GitHub Desktop.
Save kmjones1979/302c41af374bdedac53c67090b547d7f to your computer and use it in GitHub Desktop.
Updated version
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "./MentorNFT.sol";
error ZeroAddressAdmin();
error InvalidParams();
error DeploymentLimitExceeded();
error ZeroSupportDuration();
/**
* @title MentorMenteeNFTFactory (Upgradeable)
* @notice Factory contract for deploying MentorMenteeNFT instances.
* Allows authorized deployers to create their own NFT collections for token-gating.
* Uses UUPS proxy pattern for upgradeability.
* Deployment of new NFTs can be paused by an admin.
*/
contract MentorNFTFactoryImpl is
Initializable,
OwnableUpgradeable,
AccessControlUpgradeable,
UUPSUpgradeable,
PausableUpgradeable
{
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
address[] public deployedNFTs;
mapping(address => address[]) public mentorToNFTs;
address public feeAddress;
uint256 public feeBasisPoints; // Fee in basis points (0-2500 = 0-25%)
// Track total deployments for efficient counting without array length
uint256 public totalDeployments;
/**
* @notice Emitted when a new MentorMenteeNFT contract is deployed.
* @param mentor The address of the mentor who initiated the deployment.
* @param nftContract The address of the newly deployed NFT contract.
* @param name The name of the deployed NFT collection.
* @param symbol The symbol of the deployed NFT collection.
* @param maxSupply The max supply of the deployed NFT collection.
* @param mintPrice The mint price (in wei) of the deployed NFT collection.
* @param weeklyMeetings Does this tier grant weekly meeting access?
* @param minsPerWeek Minutes per week commitment for this mentorship tier.
* @param supportDurationWeeks How many weeks the mentorship support lasts.
* @param feeAddress The address of the fee recipient.
* @param feeBasisPoints The fee basis points for the deployed NFT collection.
*/
event NFTDeployed(
address indexed mentor,
address indexed nftContract,
string name,
string symbol,
uint256 maxSupply,
uint256 mintPrice,
bool weeklyMeetings,
uint256 minsPerWeek,
uint256 supportDurationWeeks,
address feeAddress,
uint256 feeBasisPoints
);
/**
* @notice Emitted when the fee address is updated.
* @param oldFeeAddress The previous fee address.
* @param newFeeAddress The new fee address.
* @param updatedBy The address that made the update.
*/
event FeeAddressUpdated(address indexed oldFeeAddress, address indexed newFeeAddress, address indexed updatedBy);
/**
* @notice Emitted when the fee basis points are updated.
* @param oldFeeBasisPoints The previous fee basis points.
* @param newFeeBasisPoints The new fee basis points.
* @param updatedBy The address that made the update.
*/
event FeeBasisPointsUpdated(uint256 oldFeeBasisPoints, uint256 newFeeBasisPoints, address indexed updatedBy);
/**
* @notice Emitted when deployer role is granted.
* @param account The account granted the role.
* @param admin The admin who granted the role.
*/
event DeployerRoleGranted(address indexed account, address indexed admin);
/**
* @notice Emitted when deployer role is revoked.
* @param account The account whose role was revoked.
* @param admin The admin who revoked the role.
*/
event DeployerRoleRevoked(address indexed account, address indexed admin);
/**
* @notice Initialize the contract.
* @param _admin The address of the admin.
*/
function initialize(address _admin) public initializer {
__Ownable_init();
__AccessControl_init();
__UUPSUpgradeable_init();
__Pausable_init();
if (_admin == address(0)) {
revert ZeroAddressAdmin();
}
feeAddress = _admin;
feeBasisPoints = 1500; // 15% in basis points
totalDeployments = 0;
_grantRole(DEFAULT_ADMIN_ROLE, _admin);
_grantRole(ADMIN_ROLE, _admin);
_grantRole(UPGRADER_ROLE, _admin);
_grantRole(DEPLOYER_ROLE, _admin);
_grantRole(PAUSER_ROLE, _admin);
_setRoleAdmin(PAUSER_ROLE, ADMIN_ROLE);
_setRoleAdmin(DEPLOYER_ROLE, ADMIN_ROLE);
}
/**
* @notice Deploys a new MentorMenteeNFT contract.
* @dev The caller (msg.sender) MUST have the DEPLOYER_ROLE.
* The caller is designated as the mentor and initial owner of the new NFT contract.
* @param _name Name for the new NFT collection.
* @param _symbol Symbol for the new NFT collection.
* @param _maxSupply Maximum supply for the new NFT collection.
* @param _mintPrice Mint price (in wei) for the new NFT collection.
* @param _minsPerWeek Mentor defined minutes per week for this tier.
* @param _weeklyMeetings Does this tier grant weekly meeting access (e.g., Calendly)?
* @param _supportDurationWeeks How many weeks does the mentorship support last?
* @return nftAddress The address of the newly deployed MentorMenteeNFT contract.
*/
function deployNFT(
string memory _name,
string memory _symbol,
uint256 _maxSupply,
uint256 _mintPrice,
uint256 _minsPerWeek,
bool _weeklyMeetings,
uint256 _supportDurationWeeks
) public onlyRole(DEPLOYER_ROLE) whenNotPaused returns (address nftAddress) {
// Enhanced validation
if (bytes(_name).length == 0 || bytes(_symbol).length == 0) revert InvalidParams();
if (_maxSupply == 0 || _maxSupply > 10000) revert InvalidParams();
if (_supportDurationWeeks == 0 || _supportDurationWeeks > 20) revert ZeroSupportDuration();
if (_minsPerWeek == 0) revert InvalidParams();
if (totalDeployments >= 1000) revert DeploymentLimitExceeded();
if (mentorToNFTs[msg.sender].length >= 10) revert DeploymentLimitExceeded();
address mentor = msg.sender;
MentorNFT newNFT = new MentorNFT(
_name,
_symbol,
_maxSupply,
_mintPrice,
mentor,
mentor,
_weeklyMeetings,
_minsPerWeek,
_supportDurationWeeks,
feeAddress,
feeBasisPoints
);
nftAddress = address(newNFT);
deployedNFTs.push(nftAddress);
mentorToNFTs[mentor].push(nftAddress);
// Use efficient counter instead of array.length
unchecked {
totalDeployments++;
}
emit NFTDeployed(
mentor,
nftAddress,
_name,
_symbol,
_maxSupply,
_mintPrice,
_weeklyMeetings,
_minsPerWeek,
_supportDurationWeeks,
feeAddress,
feeBasisPoints
);
return nftAddress;
}
/**
* @notice Get total number of deployed NFTs (gas efficient).
* @return count The total number of deployed NFTs.
*/
function getTotalDeployments() public view returns (uint256 count) {
return totalDeployments;
}
/**
* @notice Get deployed NFTs with pagination to avoid gas issues.
* @param offset Starting index.
* @param limit Maximum number of results to return.
* @return addresses Array of NFT contract addresses.
* @return hasMore Whether there are more results beyond this page.
*/
function getDeployedNFTsPaginated(
uint256 offset,
uint256 limit
) public view returns (address[] memory addresses, bool hasMore) {
uint256 totalLength = deployedNFTs.length;
if (offset >= totalLength) {
return (new address[](0), false);
}
uint256 end = offset + limit;
if (end > totalLength) {
end = totalLength;
}
uint256 resultLength = end - offset;
addresses = new address[](resultLength);
for (uint256 i = 0; i < resultLength; i++) {
addresses[i] = deployedNFTs[offset + i];
}
hasMore = end < totalLength;
}
/**
* @notice Get all NFT contracts deployed by this factory.
* @dev WARNING: This can run out of gas for large arrays. Use pagination instead.
* @return addresses An array of deployed NFT contract addresses.
*/
function getAllDeployedNFTs() public view returns (address[] memory addresses) {
return deployedNFTs;
}
/**
* @notice Get all NFT contracts deployed by a specific mentor.
* @param _mentor The address of the mentor.
* @return addresses An array of NFT contract addresses deployed by the mentor.
*/
function getNFTsByMentor(address _mentor) public view returns (address[] memory addresses) {
return mentorToNFTs[_mentor];
}
/**
* @notice Get number of NFTs deployed by a specific mentor.
* @param _mentor The address of the mentor.
* @return count The number of NFTs deployed by the mentor.
*/
function getMentorDeploymentCount(address _mentor) public view returns (uint256 count) {
return mentorToNFTs[_mentor].length;
}
/**
* @notice Set the fee address.
* @param _feeAddress The address of the fee recipient.
*/
function setFeeAddress(address _feeAddress) public onlyRole(ADMIN_ROLE) {
if (_feeAddress == address(0)) revert ZeroAddressAdmin();
address oldFeeAddress = feeAddress;
feeAddress = _feeAddress;
emit FeeAddressUpdated(oldFeeAddress, _feeAddress, msg.sender);
}
/**
* @notice Set the fee basis points.
* @param _feeBasisPoints The fee basis points for the deployed NFT collection.
*/
function setFeeBasisPoints(uint256 _feeBasisPoints) public onlyRole(ADMIN_ROLE) {
if (_feeBasisPoints > 2500) revert InvalidParams(); // Max 25%
uint256 oldFeeBasisPoints = feeBasisPoints;
feeBasisPoints = _feeBasisPoints;
emit FeeBasisPointsUpdated(oldFeeBasisPoints, _feeBasisPoints, msg.sender);
}
/**
* @notice Authorize the upgrade of the contract.
* @param newImplementation The address of the new implementation.
*/
function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {}
/**
* @notice Grant the DEPLOYER_ROLE to an account.
* @param _account The address of the account to grant the DEPLOYER_ROLE to.
*/
function grantDeployerRole(address _account) public onlyRole(ADMIN_ROLE) {
if (_account == address(0)) revert ZeroAddressAdmin();
grantRole(DEPLOYER_ROLE, _account);
emit DeployerRoleGranted(_account, msg.sender);
}
/**
* @notice Revoke the DEPLOYER_ROLE from an account.
* @param _account The address of the account to revoke the DEPLOYER_ROLE from.
*/
function revokeDeployerRole(address _account) public onlyRole(ADMIN_ROLE) {
revokeRole(DEPLOYER_ROLE, _account);
emit DeployerRoleRevoked(_account, msg.sender);
}
/**
* @notice Pause the contract.
* @dev Callable only by accounts with PAUSER_ROLE.
* Prevents actions modified by `whenNotPaused`.
*/
function pause() public virtual onlyRole(PAUSER_ROLE) {
_pause();
}
/**
* @notice Unpauses the contract.
* @dev Callable only by accounts with PAUSER_ROLE.
* Resumes actions modified by `whenNotPaused`.
*/
function unpause() public virtual onlyRole(PAUSER_ROLE) {
_unpause();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment