Skip to content

Commit 9f1a3e5

Browse files
committed
feat(story-nft): add set IP metadata when mint
1 parent fab03b2 commit 9f1a3e5

15 files changed

+164
-49
lines changed

contracts/interfaces/story-nft/IOrgNFT.sol

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity 0.8.26;
33

44
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
5+
import { WorkflowStructs } from "../../lib/WorkflowStructs.sol";
56

67
/// @title Organization NFT Interface
78
/// @notice Each organization token represents a Story ecosystem project.
@@ -46,23 +47,23 @@ interface IOrgNFT is IERC721Metadata {
4647
////////////////////////////////////////////////////////////////////////////
4748
/// @notice Mints the root organization token and register it as an IP.
4849
/// @param recipient The address of the recipient of the root organization token.
49-
/// @param tokenURI The URI of the root organization token.
50+
/// @param orgIpMetadata OPTIONAL. The desired metadata for the newly minted OrgNFT and registered IP.
5051
/// @return rootOrgTokenId The ID of the root organization token.
5152
/// @return rootOrgIpId The ID of the root organization IP.
5253
function mintRootOrgNft(
5354
address recipient,
54-
string memory tokenURI
55+
WorkflowStructs.IPMetadata calldata orgIpMetadata
5556
) external returns (uint256 rootOrgTokenId, address rootOrgIpId);
5657

5758
/// @notice Mints a organization token, register it as an IP,
5859
/// and makes the IP as a derivative of the root organization IP.
5960
/// @param recipient The address of the recipient of the minted organization token.
60-
/// @param tokenURI The URI of the minted organization token.
61+
/// @param orgIpMetadata OPTIONAL. The desired metadata for the newly minted OrgNFT and registered IP.
6162
/// @return orgTokenId The ID of the minted organization token.
6263
/// @return orgIpId The ID of the organization IP.
6364
function mintOrgNft(
6465
address recipient,
65-
string memory tokenURI
66+
WorkflowStructs.IPMetadata calldata orgIpMetadata
6667
) external returns (uint256 orgTokenId, address orgIpId);
6768

6869
/// @notice Sets the tokenURI of `tokenId` organization token.

contracts/interfaces/story-nft/IOrgStoryNFTFactory.sol

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity 0.8.26;
33

44
import { IStoryNFT } from "./IStoryNFT.sol";
5+
import { WorkflowStructs } from "../../lib/WorkflowStructs.sol";
56

67
/// @title Organization Story NFT Factory Interface
78
/// @notice Organization Story NFT Factory is the entrypoint for creating new Story NFT collections.
@@ -76,7 +77,7 @@ interface IOrgStoryNFTFactory {
7677
/// @param orgStoryNftTemplate The address of a whitelisted OrgStoryNFT template to be cloned.
7778
/// @param orgNftRecipient The address of the recipient of the organization NFT.
7879
/// @param orgName The name of the organization.
79-
/// @param orgTokenURI The token URI of the organization NFT.
80+
/// @param orgIpMetadata OPTIONAL. The desired metadata for the newly minted OrgNFT and registered IP.
8081
/// @param signature The signature from the OrgStoryNFTFactory's whitelist signer. This signautre is genreated by
8182
/// having the whitelist signer sign the caller's address (msg.sender) for this `deployOrgStoryNft` function.
8283
/// @param storyNftInitParams The initialization parameters for StoryNFT {see {IStoryNFT-StoryNftInitParams}}.
@@ -88,7 +89,7 @@ interface IOrgStoryNFTFactory {
8889
address orgStoryNftTemplate,
8990
address orgNftRecipient,
9091
string calldata orgName,
91-
string calldata orgTokenURI,
92+
WorkflowStructs.IPMetadata calldata orgIpMetadata,
9293
bytes calldata signature,
9394
IStoryNFT.StoryNftInitParams calldata storyNftInitParams
9495
) external returns (address orgNft, uint256 orgTokenId, address orgIpId, address orgStoryNft);
@@ -99,7 +100,7 @@ interface IOrgStoryNFTFactory {
99100
/// @param orgStoryNftTemplate The address of a whitelisted OrgStoryNFT template to be cloned.
100101
/// @param orgNftRecipient The address of the recipient of the organization NFT.
101102
/// @param orgName The name of the organization.
102-
/// @param orgTokenURI The token URI of the organization NFT.
103+
/// @param orgIpMetadata OPTIONAL. The desired metadata for the newly minted OrgNFT and registered IP.
103104
/// @param storyNftInitParams The initialization parameters for StoryNFT {see {IStoryNFT-StoryNftInitParams}}.
104105
/// @param isRootOrg Whether the organization is the root organization.
105106
/// @return orgNft The address of the organization NFT.
@@ -110,7 +111,7 @@ interface IOrgStoryNFTFactory {
110111
address orgStoryNftTemplate,
111112
address orgNftRecipient,
112113
string calldata orgName,
113-
string calldata orgTokenURI,
114+
WorkflowStructs.IPMetadata calldata orgIpMetadata,
114115
IStoryNFT.StoryNftInitParams calldata storyNftInitParams,
115116
bool isRootOrg
116117
) external returns (address orgNft, uint256 orgTokenId, address orgIpId, address orgStoryNft);

contracts/interfaces/story-nft/IStoryBadgeNFT.sol

+6
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,15 @@ interface IStoryBadgeNFT is IStoryNFT, IERC721Metadata, IERC5192 {
3030
/// @notice Struct for custom data for initializing the StoryBadgeNFT contract.
3131
/// @param tokenURI The token URI for all the badges (follows OpenSea metadata standard).
3232
/// @param signer The signer of the whitelist signatures.
33+
/// @param ipMetadataURI The URI of the metadata for all IP from this collection.
34+
/// @param ipMetadataHash The hash of the metadata for all IP from this collection.
35+
/// @param nftMetadataHash The hash of the metadata for all IP NFTs from this collection.
3336
struct CustomInitParams {
3437
string tokenURI;
3538
address signer;
39+
string ipMetadataURI;
40+
bytes32 ipMetadataHash;
41+
bytes32 nftMetadataHash;
3642
}
3743

3844
////////////////////////////////////////////////////////////////////////////

contracts/story-nft/BaseOrgStoryNFT.sol

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ abstract contract BaseOrgStoryNFT is IOrgStoryNFT, BaseStoryNFT {
3333
constructor(
3434
address ipAssetRegistry,
3535
address licensingModule,
36+
address coreMetadataModule,
3637
address upgradeableBeacon,
3738
address orgNft
38-
) BaseStoryNFT(ipAssetRegistry, licensingModule) {
39+
) BaseStoryNFT(ipAssetRegistry, licensingModule, coreMetadataModule) {
3940
if (orgNft == address(0)) revert StoryNFT__ZeroAddressParam();
4041
ORG_NFT = orgNft;
4142
UPGRADEABLE_BEACON = upgradeableBeacon;

contracts/story-nft/BaseStoryNFT.sol

+27-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol
55
/* solhint-disable-next-line max-line-length */
66
import { ERC721URIStorageUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
77
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
8+
import { ICoreMetadataModule } from "@storyprotocol/core/interfaces/modules/metadata/ICoreMetadataModule.sol";
89
import { IIPAssetRegistry } from "@story-protocol/protocol-core/contracts/interfaces/registries/IIPAssetRegistry.sol";
910
/*solhint-disable-next-line max-line-length*/
1011
import { ILicensingModule } from "@story-protocol/protocol-core/contracts/interfaces/modules/licensing/ILicensingModule.sol";
@@ -25,6 +26,10 @@ abstract contract BaseStoryNFT is IStoryNFT, ERC721URIStorageUpgradeable, Ownabl
2526
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
2627
ILicensingModule public immutable LICENSING_MODULE;
2728

29+
/// @notice Core Metadata Module address.
30+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
31+
ICoreMetadataModule public immutable CORE_METADATA_MODULE;
32+
2833
/// @dev Storage structure for the BaseStoryNFT
2934
/// @param contractURI The contract URI of the collection.
3035
/// @param baseURI The base URI of the collection.
@@ -40,10 +45,12 @@ abstract contract BaseStoryNFT is IStoryNFT, ERC721URIStorageUpgradeable, Ownabl
4045
bytes32 private constant BaseStoryNFTStorageLocation =
4146
0x81ed94d7560ff7bef5060a232718049e514c358c346e3254b876807a753c0e00;
4247

43-
constructor(address ipAssetRegistry, address licensingModule) {
44-
if (ipAssetRegistry == address(0) || licensingModule == address(0)) revert StoryNFT__ZeroAddressParam();
48+
constructor(address ipAssetRegistry, address licensingModule, address coreMetadataModule) {
49+
if (ipAssetRegistry == address(0) || licensingModule == address(0) || coreMetadataModule == address(0))
50+
revert StoryNFT__ZeroAddressParam();
4551
IP_ASSET_REGISTRY = IIPAssetRegistry(ipAssetRegistry);
4652
LICENSING_MODULE = ILicensingModule(licensingModule);
53+
CORE_METADATA_MODULE = ICoreMetadataModule(coreMetadataModule);
4754

4855
_disableInitializers();
4956
}
@@ -75,22 +82,38 @@ abstract contract BaseStoryNFT is IStoryNFT, ERC721URIStorageUpgradeable, Ownabl
7582
/// @return tokenId The ID of the minted token.
7683
/// @return ipId The ID of the newly created IP.
7784
function _mintAndRegisterIp(address recipient) internal virtual returns (uint256 tokenId, address ipId) {
78-
(tokenId, ipId) = _mintAndRegisterIp(recipient, "");
85+
(tokenId, ipId) = _mintAndRegisterIp(recipient, "", "", bytes32(0), bytes32(0));
7986
}
8087

8188
/// @notice Mints a new token and registers as an IP asset.
8289
/// @param recipient The address to mint the token to.
8390
/// @param tokenURI_ The token URI of the token (see {ERC721URIStorage-tokenURI} for how it is used).
91+
/// @param ipMetadataURI The URI of the metadata for the IP.
92+
/// @param ipMetadataHash The hash of the metadata for the IP.
93+
/// @param nftMetadataHash The hash of the metadata for the IP NFT.
8494
/// @return tokenId The ID of the minted token.
8595
/// @return ipId The ID of the newly created IP.
8696
function _mintAndRegisterIp(
8797
address recipient,
88-
string memory tokenURI_
98+
string memory tokenURI_,
99+
string memory ipMetadataURI,
100+
bytes32 ipMetadataHash,
101+
bytes32 nftMetadataHash
89102
) internal virtual returns (uint256 tokenId, address ipId) {
90103
tokenId = _getBaseStoryNFTStorage().totalSupply++;
91104
_safeMint(recipient, tokenId);
92105
_setTokenURI(tokenId, tokenURI_);
106+
93107
ipId = IP_ASSET_REGISTRY.register(block.chainid, address(this), tokenId);
108+
109+
// set the IP metadata if they are not empty
110+
if (
111+
keccak256(abi.encodePacked(ipMetadataURI)) != keccak256("") ||
112+
ipMetadataHash != bytes32(0) ||
113+
nftMetadataHash != bytes32(0)
114+
) {
115+
ICoreMetadataModule(CORE_METADATA_MODULE).setAll(ipId, ipMetadataURI, ipMetadataHash, nftMetadataHash);
116+
}
94117
}
95118

96119
/// @notice Register `ipId` as a derivative of `parentIpIds` under `licenseTemplate` with `licenseTermsIds`.

contracts/story-nft/OrgNFT.sol

+39-9
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721H
88
import { ERC721URIStorageUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
99
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
1010
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
11+
import { ICoreMetadataModule } from "@storyprotocol/core/interfaces/modules/metadata/ICoreMetadataModule.sol";
1112
import { IIPAssetRegistry } from "@storyprotocol/core/interfaces/registries/IIPAssetRegistry.sol";
1213
// solhint-disable-next-line max-line-length
1314
import { ILicensingModule } from "@story-protocol/protocol-core/contracts/interfaces/modules/licensing/ILicensingModule.sol";
1415

1516
import { IOrgNFT } from "../interfaces/story-nft/IOrgNFT.sol";
17+
import { WorkflowStructs } from "../lib/WorkflowStructs.sol";
1618

1719
/// @title Organization NFT
1820
/// @notice Each organization token represents a Story ecosystem project.
@@ -22,18 +24,27 @@ contract OrgNFT is IOrgNFT, ERC721URIStorageUpgradeable, AccessManagedUpgradeabl
2224
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
2325

2426
/// @notice Story Proof-of-Creativity IP Asset Registry address.
27+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
2528
IIPAssetRegistry public immutable IP_ASSET_REGISTRY;
2629

2730
/// @notice Story Proof-of-Creativity Licensing Module address.
31+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
2832
ILicensingModule public immutable LICENSING_MODULE;
2933

34+
/// @notice Core Metadata Module address.
35+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
36+
ICoreMetadataModule public immutable CORE_METADATA_MODULE;
37+
3038
/// @notice License template address.
39+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
3140
address public immutable LICENSE_TEMPLATE;
3241

3342
/// @notice License terms ID.
43+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
3444
uint256 public immutable LICENSE_TERMS_ID;
3545

3646
/// @notice Story NFT Factory address.
47+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
3748
address public immutable ORG_STORY_NFT_FACTORY;
3849

3950
/// @dev Storage structure for the OrgNFT
@@ -59,13 +70,15 @@ contract OrgNFT is IOrgNFT, ERC721URIStorageUpgradeable, AccessManagedUpgradeabl
5970
constructor(
6071
address ipAssetRegistry,
6172
address licensingModule,
73+
address coreMetadataModule,
6274
address orgStoryNftFactory,
6375
address licenseTemplate,
6476
uint256 licenseTermsId
6577
) {
6678
if (
6779
ipAssetRegistry == address(0) ||
6880
licensingModule == address(0) ||
81+
coreMetadataModule == address(0) ||
6982
orgStoryNftFactory == address(0) ||
7083
licenseTemplate == address(0)
7184
) revert OrgNFT__ZeroAddressParam();
@@ -75,6 +88,7 @@ contract OrgNFT is IOrgNFT, ERC721URIStorageUpgradeable, AccessManagedUpgradeabl
7588
ORG_STORY_NFT_FACTORY = orgStoryNftFactory;
7689
LICENSE_TEMPLATE = licenseTemplate;
7790
LICENSE_TERMS_ID = licenseTermsId;
91+
CORE_METADATA_MODULE = ICoreMetadataModule(coreMetadataModule);
7892

7993
_disableInitializers();
8094
}
@@ -93,36 +107,38 @@ contract OrgNFT is IOrgNFT, ERC721URIStorageUpgradeable, AccessManagedUpgradeabl
93107
/// @notice Mints the root organization token and register it as an IP.
94108
/// @dev This function is only callable by the OrgStoryNFTFactory contract.
95109
/// @param recipient The address of the recipient of the root organization token.
96-
/// @param tokenURI_ The URI of the root organization token.
110+
/// @param orgIpMetadata OPTIONAL. The desired metadata for the newly minted OrgNFT and registered IP.
97111
/// @return rootOrgTokenId The ID of the root organization token.
98112
/// @return rootOrgIpId The ID of the root organization IP.
99113
function mintRootOrgNft(
100114
address recipient,
101-
string memory tokenURI_
115+
WorkflowStructs.IPMetadata calldata orgIpMetadata
102116
) external onlyStoryNFTFactory returns (uint256 rootOrgTokenId, address rootOrgIpId) {
103117
OrgNFTStorage storage $ = _getOrgNFTStorage();
104118
if ($.rootOrgIpId != address(0)) revert OrgNFT__RootOrgNftAlreadyMinted();
105119

106-
(rootOrgTokenId, rootOrgIpId) = _mintAndRegisterIp(recipient, tokenURI_);
120+
(rootOrgTokenId, rootOrgIpId) = _mintAndRegisterIp(address(this), orgIpMetadata);
107121
$.rootOrgIpId = rootOrgIpId;
122+
123+
_safeTransfer(address(this), recipient, rootOrgTokenId);
108124
}
109125

110126
/// @notice Mints a organization token, register it as an IP,
111127
/// and makes the IP as a derivative of the root organization IP.
112128
/// @dev This function is only callable by the OrgStoryNFTFactory contract.
113129
/// @param recipient The address of the recipient of the minted organization token.
114-
/// @param tokenURI_ The URI of the minted organization token.
130+
/// @param orgIpMetadata OPTIONAL. The desired metadata for the newly minted OrgNFT and registered IP.
115131
/// @return orgTokenId The ID of the minted organization token.
116132
/// @return orgIpId The ID of the organization IP.
117133
function mintOrgNft(
118134
address recipient,
119-
string memory tokenURI_
135+
WorkflowStructs.IPMetadata calldata orgIpMetadata
120136
) external onlyStoryNFTFactory returns (uint256 orgTokenId, address orgIpId) {
121137
OrgNFTStorage storage $ = _getOrgNFTStorage();
122138
if ($.rootOrgIpId == address(0)) revert OrgNFT__RootOrgNftNotMinted();
123139

124140
// Mint the organization token and register it as an IP.
125-
(orgTokenId, orgIpId) = _mintAndRegisterIp(address(this), tokenURI_);
141+
(orgTokenId, orgIpId) = _mintAndRegisterIp(address(this), orgIpMetadata);
126142

127143
address[] memory parentIpIds = new address[](1);
128144
uint256[] memory licenseTermsIds = new uint256[](1);
@@ -152,19 +168,33 @@ contract OrgNFT is IOrgNFT, ERC721URIStorageUpgradeable, AccessManagedUpgradeabl
152168

153169
/// @notice Mints a organization token and register it as an IP.
154170
/// @param recipient The address of the recipient of the minted organization token.
155-
/// @param tokenURI_ The URI of the minted organization token.
171+
/// @param orgIpMetadata OPTIONAL. The desired metadata for the newly minted OrgNFT and registered IP.
156172
/// @return orgTokenId The ID of the minted organization token.
157173
/// @return orgIpId The ID of the organization IP.
158174
function _mintAndRegisterIp(
159175
address recipient,
160-
string memory tokenURI_
176+
WorkflowStructs.IPMetadata calldata orgIpMetadata
161177
) private returns (uint256 orgTokenId, address orgIpId) {
162178
OrgNFTStorage storage $ = _getOrgNFTStorage();
163179
orgTokenId = $.totalSupply++;
164180
_safeMint(recipient, orgTokenId);
165-
_setTokenURI(orgTokenId, tokenURI_);
181+
_setTokenURI(orgTokenId, orgIpMetadata.nftMetadataURI);
166182
orgIpId = IP_ASSET_REGISTRY.register(block.chainid, address(this), orgTokenId);
167183

184+
// set the IP metadata if they are not empty
185+
if (
186+
keccak256(abi.encodePacked(orgIpMetadata.ipMetadataURI)) != keccak256("") ||
187+
orgIpMetadata.ipMetadataHash != bytes32(0) ||
188+
orgIpMetadata.nftMetadataHash != bytes32(0)
189+
) {
190+
ICoreMetadataModule(CORE_METADATA_MODULE).setAll(
191+
orgIpId,
192+
orgIpMetadata.ipMetadataURI,
193+
orgIpMetadata.ipMetadataHash,
194+
orgIpMetadata.nftMetadataHash
195+
);
196+
}
197+
168198
emit OrgNFTMinted(recipient, address(this), orgTokenId, orgIpId);
169199
}
170200

0 commit comments

Comments
 (0)