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

feat(spg & spgnft): add support for base URI #66

Merged
merged 3 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
144 changes: 76 additions & 68 deletions contracts/SPGNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,25 @@ import { SPGNFTLib } from "./lib/SPGNFTLib.sol";

contract SPGNFT is ISPGNFT, ERC721URIStorageUpgradeable, AccessControlUpgradeable {
/// @dev Storage structure for the SPGNFTSotrage.
/// @param maxSupply The maximum supply of the collection.
/// @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.
/// @param publicMinting True if the collection is open for everyone to mint.
/// @param _maxSupply The maximum supply of the collection.
/// @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.
/// @param _publicMinting True if the collection is open for everyone to mint.
/// @param _baseURI The base URI for the collection. If baseURI is not empty, tokenURI will be
/// either baseURI + token ID (if nftMetadataURI is empty) or baseURI + nftMetadataURI.
/// @custom:storage-location erc7201:story-protocol-periphery.SPGNFT
struct SPGNFTStorage {
uint32 maxSupply;
uint32 totalSupply;
uint256 mintFee;
address mintFeeToken;
address mintFeeRecipient;
bool mintOpen;
bool publicMinting;
uint32 _maxSupply;
uint32 _totalSupply;
uint256 _mintFee;
address _mintFeeToken;
address _mintFeeRecipient;
bool _mintOpen;
bool _publicMinting;
string _baseURI;
}

// keccak256(abi.encode(uint256(keccak256("story-protocol-periphery.SPGNFT")) - 1)) & ~bytes32(uint256(0xff));
Expand Down Expand Up @@ -57,126 +60,123 @@ contract SPGNFT is ISPGNFT, ERC721URIStorageUpgradeable, AccessControlUpgradeabl
_disableInitializers();
}

/// @dev Initializes the NFT collection.
/// @dev Initializes the SPGNFT collection.
/// @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 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 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 mintFeeRecipient,
address owner,
bool mintOpen,
bool isPublicMinting
) public initializer {
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);
/// @param initParams The initialization parameters for the collection. See {ISPGNFT-InitParams}
function initialize(ISPGNFT.InitParams calldata initParams) public initializer {
if (initParams.mintFee > 0 && initParams.mintFeeToken == address(0)) revert Errors.SPGNFT__ZeroAddressParam();
if (initParams.maxSupply == 0) revert Errors.SPGNFT__ZeroMaxSupply();

_grantRole(SPGNFTLib.ADMIN_ROLE, initParams.owner);
_grantRole(SPGNFTLib.MINTER_ROLE, initParams.owner);

// grant roles to SPG
if (owner != SPG_ADDRESS) {
if (initParams.owner != SPG_ADDRESS) {
_grantRole(SPGNFTLib.ADMIN_ROLE, SPG_ADDRESS);
_grantRole(SPGNFTLib.MINTER_ROLE, SPG_ADDRESS);
}

SPGNFTStorage storage $ = _getSPGNFTStorage();
$.maxSupply = maxSupply;
$.mintFee = mintFee;
$.mintFeeToken = mintFeeToken;
$.mintFeeRecipient = mintFeeRecipient;
$.mintOpen = mintOpen;
$.publicMinting = isPublicMinting;
$._maxSupply = initParams.maxSupply;
$._mintFee = initParams.mintFee;
$._mintFeeToken = initParams.mintFeeToken;
$._mintFeeRecipient = initParams.mintFeeRecipient;
$._mintOpen = initParams.mintOpen;
$._publicMinting = initParams.isPublicMinting;
$._baseURI = initParams.baseURI;

__ERC721_init(name, symbol);
__ERC721_init(initParams.name, initParams.symbol);
}

/// @notice Returns the total minted supply of the collection.
function totalSupply() public view returns (uint256) {
return uint256(_getSPGNFTStorage().totalSupply);
return uint256(_getSPGNFTStorage()._totalSupply);
}

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

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

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

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

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

/// @notice Returns the base URI for the collection.
/// @dev If baseURI is not empty, tokenURI will be either or baseURI + nftMetadataURI
/// or baseURI + token ID (if nftMetadataURI is empty).
function baseURI() external view returns (string memory) {
return _baseURI();
}

/// @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;
_getSPGNFTStorage()._mintFee = fee;
}

/// @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) public onlyRole(SPGNFTLib.ADMIN_ROLE) {
_getSPGNFTStorage().mintFeeToken = token;
_getSPGNFTStorage()._mintFeeToken = token;
}

/// @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) {
if (msg.sender != _getSPGNFTStorage()._mintFeeRecipient) {
revert Errors.SPGNFT__CallerNotFeeRecipient();
}
_getSPGNFTStorage().mintFeeRecipient = newFeeRecipient;
_getSPGNFTStorage()._mintFeeRecipient = newFeeRecipient;
}

/// @notice Sets the minting status.
/// @dev Only callable by the admin role.
/// @param mintOpen Whether minting is open or not.
function setMintOpen(bool mintOpen) public onlyRole(SPGNFTLib.ADMIN_ROLE) {
_getSPGNFTStorage().mintOpen = mintOpen;
_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;
_getSPGNFTStorage()._publicMinting = isPublicMinting;
}

/// @notice Sets the base URI for the collection. If baseURI is not empty, tokenURI will be
/// either baseURI + token ID (if nftMetadataURI is empty) or baseURI + nftMetadataURI.
/// @dev Only callable by the admin role.
/// @param baseURI The new base URI for the collection.
function setBaseURI(string memory baseURI) public onlyRole(SPGNFTLib.ADMIN_ROLE) {
_getSPGNFTStorage()._baseURI = baseURI;
}

/// @notice Mints an NFT from the collection. Only callable by the minter role.
/// @param to The address of the recipient of the minted NFT.
/// @param nftMetadataURI OPTIONAL. The URI of the desired metadata for the newly minted NFT.
/// @return tokenId The ID of the minted NFT.
function mint(address to, string calldata nftMetadataURI) public virtual returns (uint256 tokenId) {
if (!_getSPGNFTStorage().publicMinting && !hasRole(SPGNFTLib.MINTER_ROLE, msg.sender)) {
if (!_getSPGNFTStorage()._publicMinting && !hasRole(SPGNFTLib.MINTER_ROLE, msg.sender)) {
revert Errors.SPGNFT__MintingDenied();
}
tokenId = _mintToken({ to: to, payer: msg.sender, nftMetadataURI: nftMetadataURI });
Expand All @@ -198,7 +198,7 @@ contract SPGNFT is ISPGNFT, ERC721URIStorageUpgradeable, AccessControlUpgradeabl
/// @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)));
IERC20(token).transfer(_getSPGNFTStorage()._mintFeeRecipient, IERC20(token).balanceOf(address(this)));
}

/// @dev Supports ERC165 interface.
Expand All @@ -216,19 +216,27 @@ contract SPGNFT is ISPGNFT, ERC721URIStorageUpgradeable, AccessControlUpgradeabl
/// @return tokenId The ID of the minted NFT.
function _mintToken(address to, address payer, string calldata nftMetadataURI) internal returns (uint256 tokenId) {
SPGNFTStorage storage $ = _getSPGNFTStorage();
if (!$.mintOpen) revert Errors.SPGNFT__MintingClosed();
if ($.totalSupply + 1 > $.maxSupply) revert Errors.SPGNFT__MaxSupplyReached();
if (!$._mintOpen) revert Errors.SPGNFT__MintingClosed();
if ($._totalSupply + 1 > $._maxSupply) revert Errors.SPGNFT__MaxSupplyReached();

if ($.mintFeeToken != address(0) && $.mintFee > 0) {
IERC20($.mintFeeToken).transferFrom(payer, address(this), $.mintFee);
if ($._mintFeeToken != address(0) && $._mintFee > 0) {
IERC20($._mintFeeToken).transferFrom(payer, address(this), $._mintFee);
}

tokenId = ++$.totalSupply;
tokenId = ++$._totalSupply;
_mint(to, tokenId);

if (bytes(nftMetadataURI).length > 0) _setTokenURI(tokenId, nftMetadataURI);
}

/// @dev Base URI for computing tokenURI.
/// @dev If baseURI is not empty, tokenURI will be either or baseURI + nftMetadataURI
/// or baseURI + token ID (if nftMetadataURI is empty).
/// @return baseURI The base URI for the collection.
function _baseURI() internal view override returns (string memory) {
return _getSPGNFTStorage()._baseURI;
}

//
// Upgrade
//
Expand Down
43 changes: 7 additions & 36 deletions contracts/StoryProtocolGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -115,42 +115,13 @@ contract StoryProtocolGateway is
UpgradeableBeacon(_getSPGStorage().nftContractBeacon).upgradeTo(newNftContract);
}

/// @notice Creates a new NFT collection to be used by SPG.
/// @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 mintFeeToken The token to be used for mint payment.
/// @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 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,
mintFeeRecipient,
owner,
mintOpen,
isPublicMinting
);
emit CollectionCreated(nftContract);
/// @notice Creates a new SPGNFT collection to be used by SPG.
/// @param spgNftInitParams The initialization parameters for the SPGNFT collection. See {ISPGNFT-InitParams}.
/// @return spgNftContract The address of the newly created SPGNFT collection.
function createCollection(ISPGNFT.InitParams calldata spgNftInitParams) external returns (address spgNftContract) {
spgNftContract = address(new BeaconProxy(_getSPGStorage().nftContractBeacon, ""));
ISPGNFT(spgNftContract).initialize(spgNftInitParams);
emit CollectionCreated(spgNftContract);
}

/// @notice Mint an NFT from a SPGNFT collection and register it with metadata as an IP.
Expand Down
43 changes: 31 additions & 12 deletions contracts/interfaces/ISPGNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";

interface ISPGNFT is IAccessControl, IERC721, IERC721Metadata {
/// @dev Initializes the NFT collection.
/// @notice Struct for initializing the NFT collection.
/// @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 baseURI The base URI for the collection. If baseURI is not empty, tokenURI will be
/// either baseURI + token ID (if nftMetadataURI is empty) or baseURI + nftMetadataURI.
/// @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.
Expand All @@ -18,17 +20,23 @@ interface ISPGNFT is IAccessControl, IERC721, IERC721Metadata {
/// @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 mintFeeRecipient,
address owner,
bool mintOpen,
bool isPublicMinting
) external;
struct InitParams {
string name;
string symbol;
string baseURI;
uint32 maxSupply;
uint256 mintFee;
address mintFeeToken;
address mintFeeRecipient;
address owner;
bool mintOpen;
bool isPublicMinting;
}

/// @dev Initializes the NFT collection.
/// @dev If mint fee is non-zero, mint token must be set.
/// @param params The initialization parameters. See `InitParams`.
function initialize(InitParams calldata params) external;

/// @notice Returns the total minted supply of the collection.
function totalSupply() external view returns (uint256);
Expand All @@ -48,6 +56,11 @@ interface ISPGNFT is IAccessControl, IERC721, IERC721Metadata {
/// @notice Returns true if the collection is open for public minting.
function publicMinting() external view returns (bool);

/// @notice Returns the base URI for the collection.
/// @dev If baseURI is not empty, tokenURI will be either or baseURI + nftMetadataURI
/// or baseURI + token ID (if nftMetadataURI is empty).
function baseURI() external view returns (string memory);

/// @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.
Expand All @@ -73,6 +86,12 @@ interface ISPGNFT is IAccessControl, IERC721, IERC721Metadata {
/// @param isPublicMinting Whether the collection is open for public minting or not.
function setPublicMinting(bool isPublicMinting) external;

/// @notice Sets the base URI for the collection. If baseURI is not empty, tokenURI will be
/// either baseURI + token ID (if nftMetadataURI is empty) or baseURI + nftMetadataURI.
/// @dev Only callable by the admin role.
/// @param baseURI The new base URI for the collection.
function setBaseURI(string memory baseURI) 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.
/// @param nftMetadataURI OPTIONAL. The desired metadata for the newly minted NFT.
Expand Down
Loading
Loading