Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Public minting fee recipient control #16

Merged
merged 6 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 74 additions & 18 deletions contracts/SPGNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ contract SPGNFT is ISPGNFT, ERC721Upgradeable, AccessControlUpgradeable {
/// @param totalSupply The total minted supply of the collection.
/// @param mintFee The fee to mint an NFT from the collection.
/// @param mintFeeToken The token to pay for minting.
/// @param mintFeeRecipient The address to receive mint fees.
/// @param mintOpen The status of minting, whether it is open or not.
/// @custom:storage-location erc7201:story-protocol-periphery.SPGNFT
struct SPGNFTStorage {
uint32 maxSupply;
uint32 totalSupply;
uint256 mintFee;
address mintFeeToken;
address mintFeeRecipient;
bool mintOpen;
bool publicMinting;
}

// keccak256(abi.encode(uint256(keccak256("story-protocol-periphery.SPGNFT")) - 1)) & ~bytes32(uint256(0xff));
Expand Down Expand Up @@ -50,18 +55,24 @@ contract SPGNFT is ISPGNFT, ERC721Upgradeable, AccessControlUpgradeable {
/// @param maxSupply The maximum supply of the collection.
/// @param mintFee The fee to mint an NFT from the collection.
/// @param mintFeeToken The token to pay for minting.
/// @param owner The owner of the collection.
/// @param mintFeeRecipient The address to receive mint fees.
/// @param owner The owner of the collection. Zero address indicates no owner.
/// @param mintOpen Whether the collection is open for minting on creation. Configurable by the owner.
/// @param isPublicMinting If true, anyone can mint from the collection. If false, only the addresses with the
/// minter role can mint. Configurable by the owner.
function initialize(
string memory name,
string memory symbol,
uint32 maxSupply,
uint256 mintFee,
address mintFeeToken,
address owner
address mintFeeRecipient,
address owner,
bool mintOpen,
bool isPublicMinting
) public initializer {
if (owner == address(0) || (mintFee > 0 && mintFeeToken == address(0)))
revert Errors.SPGNFT__ZeroAddressParam();
if (maxSupply == 0) revert Errors.SPGNFT_ZeroMaxSupply();
if (mintFee > 0 && mintFeeToken == address(0)) revert Errors.SPGNFT__ZeroAddressParam();
if (maxSupply == 0) revert Errors.SPGNFT__ZeroMaxSupply();

_grantRole(SPGNFTLib.ADMIN_ROLE, owner);
_grantRole(SPGNFTLib.MINTER_ROLE, owner);
Expand All @@ -76,6 +87,9 @@ contract SPGNFT is ISPGNFT, ERC721Upgradeable, AccessControlUpgradeable {
$.maxSupply = maxSupply;
$.mintFee = mintFee;
$.mintFeeToken = mintFeeToken;
$.mintFeeRecipient = mintFeeRecipient;
$.mintOpen = mintOpen;
$.publicMinting = isPublicMinting;

__ERC721_init(name, symbol);
}
Expand All @@ -85,14 +99,36 @@ contract SPGNFT is ISPGNFT, ERC721Upgradeable, AccessControlUpgradeable {
return uint256(_getSPGNFTStorage().totalSupply);
}

/// @notice Returns the current mint fee of the collection.
function mintFee() public view returns (uint256) {
return _getSPGNFTStorage().mintFee;
}

/// @notice Returns the current mint token of the collection.
function mintFeeToken() public view returns (address) {
return _getSPGNFTStorage().mintFeeToken;
}

/// @notice Returns the current mint fee of the collection.
function mintFee() public view returns (uint256) {
return _getSPGNFTStorage().mintFee;
/// @notice Returns the current mint fee recipient of the collection.
function mintFeeRecipient() public view returns (address) {
return _getSPGNFTStorage().mintFeeRecipient;
}

/// @notice Returns true if the collection is open for minting.
function mintOpen() public view returns (bool) {
return _getSPGNFTStorage().mintOpen;
}

/// @notice Returns true if the collection is open for public minting.
function publicMinting() public view returns (bool) {
return _getSPGNFTStorage().publicMinting;
}

/// @notice Sets the fee to mint an NFT from the collection. Payment is in the designated currency.
/// @dev Only callable by the admin role.
/// @param fee The new mint fee paid in the mint token.
function setMintFee(uint256 fee) public onlyRole(SPGNFTLib.ADMIN_ROLE) {
_getSPGNFTStorage().mintFee = fee;
}

/// @notice Sets the mint token for the collection.
Expand All @@ -102,17 +138,37 @@ contract SPGNFT is ISPGNFT, ERC721Upgradeable, AccessControlUpgradeable {
_getSPGNFTStorage().mintFeeToken = token;
}

/// @notice Sets the fee to mint an NFT from the collection. Payment is in the designated currency.
/// @notice Sets the recipient of mint fees.
/// @dev Only callable by the fee recipient.
/// @param newFeeRecipient The new fee recipient.
function setMintFeeRecipient(address newFeeRecipient) public {
if (msg.sender != _getSPGNFTStorage().mintFeeRecipient) {
revert Errors.SPGNFT__CallerNotFeeRecipient();
}
_getSPGNFTStorage().mintFeeRecipient = newFeeRecipient;
}

/// @notice Sets the minting status.
/// @dev Only callable by the admin role.
/// @param fee The new mint fee paid in the mint token.
function setMintFee(uint256 fee) public onlyRole(SPGNFTLib.ADMIN_ROLE) {
_getSPGNFTStorage().mintFee = fee;
/// @param mintOpen Whether minting is open or not.
function setMintOpen(bool mintOpen) public onlyRole(SPGNFTLib.ADMIN_ROLE) {
_getSPGNFTStorage().mintOpen = mintOpen;
}

/// @notice Sets the public minting status.
/// @dev Only callable by the admin role.
/// @param isPublicMinting Whether the collection is open for public minting or not.
function setPublicMinting(bool isPublicMinting) public onlyRole(SPGNFTLib.ADMIN_ROLE) {
_getSPGNFTStorage().publicMinting = isPublicMinting;
}

/// @notice Mints an NFT from the collection. Only callable by the minter role.
/// @param to The address of the recipient of the minted NFT.
/// @return tokenId The ID of the minted NFT.
function mint(address to) public onlyRole(SPGNFTLib.MINTER_ROLE) returns (uint256 tokenId) {
function mint(address to) public returns (uint256 tokenId) {
if (!_getSPGNFTStorage().publicMinting && !hasRole(SPGNFTLib.MINTER_ROLE, msg.sender)) {
revert Errors.SPGNFT__MintingDenied();
}
tokenId = _mintToken({ to: to, payer: msg.sender });
}

Expand All @@ -124,11 +180,10 @@ contract SPGNFT is ISPGNFT, ERC721Upgradeable, AccessControlUpgradeable {
tokenId = _mintToken({ to: to, payer: payer });
}

/// @dev Withdraws the contract's token balance to the recipient.
/// @param recipient The token to withdraw.
/// @param recipient The address to receive the withdrawn balance.
function withdrawToken(address token, address recipient) public onlyRole(SPGNFTLib.ADMIN_ROLE) {
IERC20(token).transfer(recipient, IERC20(token).balanceOf(address(this)));
/// @dev Withdraws the contract's token balance to the fee recipient.
/// @param token The token to withdraw.
function withdrawToken(address token) public {
IERC20(token).transfer(_getSPGNFTStorage().mintFeeRecipient, IERC20(token).balanceOf(address(this)));
}

/// @dev Supports ERC165 interface.
Expand All @@ -145,6 +200,7 @@ contract SPGNFT is ISPGNFT, ERC721Upgradeable, AccessControlUpgradeable {
/// @return tokenId The ID of the minted NFT.
function _mintToken(address to, address payer) internal returns (uint256 tokenId) {
SPGNFTStorage storage $ = _getSPGNFTStorage();
if (!$.mintOpen) revert Errors.SPGNFT__MintingClosed();
if ($.totalSupply + 1 > $.maxSupply) revert Errors.SPGNFT__MaxSupplyReached();

if ($.mintFeeToken != address(0) && $.mintFee > 0) {
Expand Down
23 changes: 20 additions & 3 deletions contracts/StoryProtocolGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -116,18 +116,35 @@ contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable
/// @param maxSupply The maximum supply of the collection.
/// @param mintFee The cost to mint an NFT from the collection.
/// @param mintFeeToken The token to be used for mint payment.
/// @param owner The owner of the collection.
/// @param mintFeeRecipient The address to receive mint fees.
/// @param owner The owner of the collection. Zero address indicates no owner.
/// @param mintOpen Whether the collection is open for minting on creation. Configurable by the owner.
/// @param isPublicMinting If true, anyone can mint from the collection. If false, only the addresses with the
/// minter role can mint. Configurable by the owner.
/// @return nftContract The address of the newly created NFT collection.
function createCollection(
string calldata name,
string calldata symbol,
uint32 maxSupply,
uint256 mintFee,
address mintFeeToken,
address owner
address mintFeeRecipient,
address owner,
bool mintOpen,
bool isPublicMinting
) external returns (address nftContract) {
nftContract = address(new BeaconProxy(_getSPGStorage().nftContractBeacon, ""));
ISPGNFT(nftContract).initialize(name, symbol, maxSupply, mintFee, mintFeeToken, owner);
ISPGNFT(nftContract).initialize(
name,
symbol,
maxSupply,
mintFee,
mintFeeToken,
mintFeeRecipient,
owner,
mintOpen,
isPublicMinting
);
emit CollectionCreated(nftContract);
}

Expand Down
56 changes: 43 additions & 13 deletions contracts/interfaces/ISPGNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,71 @@ import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions

interface ISPGNFT is IAccessControl, IERC721, IERC721Metadata {
/// @dev Initializes the NFT collection.
/// @dev If mint cost is non-zero, mint token must be set.
/// @dev If mint fee is non-zero, mint token must be set.
/// @param name The name of the collection.
/// @param symbol The symbol of the collection.
/// @param maxSupply The maximum supply of the collection.
/// @param mintFee The cost to mint an NFT from the collection.
/// @param mintFee The fee to mint an NFT from the collection.
/// @param mintFeeToken The token to pay for minting.
/// @param owner The owner of the collection.
/// @param mintFeeRecipient The address to receive mint fees.
/// @param owner The owner of the collection. Zero address indicates no owner.
/// @param mintOpen Whether the collection is open for minting on creation. Configurable by the owner.
/// @param isPublicMinting If true, anyone can mint from the collection. If false, only the addresses with the
/// minter role can mint. Configurable by the owner.
function initialize(
string memory name,
string memory symbol,
uint32 maxSupply,
uint256 mintFee,
address mintFeeToken,
address owner
address mintFeeRecipient,
address owner,
bool mintOpen,
bool isPublicMinting
) external;

/// @notice Returns the total minted supply of the collection.
function totalSupply() external view returns (uint256);

/// @notice Returns the current mint fee of the collection.
function mintFee() external view returns (uint256);

/// @notice Returns the current mint token of the collection.
function mintFeeToken() external view returns (address);

/// @notice Returns the current mint fee of the collection.
function mintFee() external view returns (uint256);
/// @notice Returns the current mint fee recipient of the collection.
function mintFeeRecipient() external view returns (address);

/// @notice Returns true if the collection is open for minting.
function mintOpen() external view returns (bool);

/// @notice Returns true if the collection is open for public minting.
function publicMinting() external view returns (bool);

/// @notice Sets the fee to mint an NFT from the collection. Payment is in the designated currency.
/// @dev Only callable by the admin role.
/// @param fee The new mint fee paid in the mint token.
function setMintFee(uint256 fee) external;

/// @notice Sets the mint token for the collection.
/// @dev Only callable by the admin role.
/// @param token The new mint token for mint payment.
function setMintFeeToken(address token) external;

/// @notice Sets the fee to mint an NFT from the collection. Payment is in the designated currency.
/// @notice Sets the recipient of mint fees.
/// @dev Only callable by the fee recipient.
/// @param newFeeRecipient The new fee recipient.
function setMintFeeRecipient(address newFeeRecipient) external;

/// @notice Sets the minting status.
/// @dev Only callable by the admin role.
/// @param fee The new mint fee paid in the mint token.
function setMintFee(uint256 fee) external;
/// @param mintOpen Whether minting is open or not.
function setMintOpen(bool mintOpen) external;

/// @notice Sets the public minting status.
/// @dev Only callable by the admin role.
/// @param isPublicMinting Whether the collection is open for public minting or not.
function setPublicMinting(bool isPublicMinting) external;

/// @notice Mints an NFT from the collection. Only callable by the minter role.
/// @param to The address of the recipient of the minted NFT.
Expand All @@ -53,8 +84,7 @@ interface ISPGNFT is IAccessControl, IERC721, IERC721Metadata {
/// @return tokenId The ID of the minted NFT.
function mintBySPG(address to, address payer) external returns (uint256 tokenId);

/// @dev Withdraws the contract's token balance to the recipient.
/// @param recipient The token to withdraw.
/// @param recipient The address to receive the withdrawn balance.
function withdrawToken(address token, address recipient) external;
/// @dev Withdraws the contract's token balance to the fee recipient.
/// @param token The token to withdraw.
function withdrawToken(address token) external;
}
11 changes: 9 additions & 2 deletions contracts/interfaces/IStoryProtocolGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,22 @@ interface IStoryProtocolGateway {
/// @param maxSupply The maximum supply of the collection.
/// @param mintFee The cost to mint an NFT from the collection.
/// @param mintFeeToken The token to be used for mint payment.
/// @param owner The owner of the collection.
/// @param mintFeeRecipient The address to receive mint fees.
/// @param owner The owner of the collection. Zero address indicates no owner.
/// @param mintOpen Whether the collection is open for minting on creation. Configurable by the owner.
/// @param isPublicMinting If true, anyone can mint from the collection. If false, only the addresses with the
/// minter role can mint. Configurable by the owner.
/// @return nftContract The address of the newly created NFT collection.
function createCollection(
string calldata name,
string calldata symbol,
uint32 maxSupply,
uint256 mintFee,
address mintFeeToken,
address owner
address mintFeeRecipient,
address owner,
bool mintOpen,
bool isPublicMinting
) external returns (address nftContract);

/// @notice Mint an NFT from a collection and register it with metadata as an IP.
Expand Down
11 changes: 10 additions & 1 deletion contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,20 @@ library Errors {
error SPGNFT__ZeroAddressParam();

/// @notice Zero max supply provided.
error SPGNFT_ZeroMaxSupply();
error SPGNFT__ZeroMaxSupply();

/// @notice Max mint supply reached.
error SPGNFT__MaxSupplyReached();

/// @notice Minting is denied if the public minting is false (=> private) but caller does not have the minter role.
error SPGNFT__MintingDenied();

/// @notice Caller is not the StoryProtocolGateway.
error SPGNFT__CallerNotSPG();

/// @notice Caller is not the fee recipient.
error SPGNFT__CallerNotFeeRecipient();

/// @notice Minting is closed.
error SPGNFT__MintingClosed();
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@story-protocol/protocol-periphery",
"version": "v1.0.0-rc1",
"version": "v1.0.0",
"description": "Story Protocol periphery smart contracts",
"main": "",
"directories": {
Expand Down Expand Up @@ -40,7 +40,8 @@
"@openzeppelin/contracts": "5.0.1",
"@openzeppelin/contracts-upgradeable": "5.0.1",
"@story-protocol/create3-deployer": "github:storyprotocol/create3-deployer#main",
"@story-protocol/protocol-core": "github:storyprotocol/protocol-core-v1#main",
"@story-protocol/protocol-core": "github:storyprotocol/protocol-core-v1#v1.0.0",
"erc6551": "^0.3.1",
"solady": "^0.0.192"
}
}
Loading
Loading