@@ -22,6 +22,7 @@ contract SPGNFT is ISPGNFT, ERC721URIStorageUpgradeable, AccessControlUpgradeabl
22
22
/// @param _publicMinting True if the collection is open for everyone to mint.
23
23
/// @param _baseURI The base URI for the collection. If baseURI is not empty, tokenURI will be
24
24
/// either baseURI + token ID (if nftMetadataURI is empty) or baseURI + nftMetadataURI.
25
+ /// @param _nftMetadataHashToTokenId The mapping of nftMetadataHash to token ID.
25
26
/// @custom:storage-location erc7201:story-protocol-periphery.SPGNFT
26
27
struct SPGNFTStorage {
27
28
uint32 _maxSupply;
@@ -33,6 +34,7 @@ contract SPGNFT is ISPGNFT, ERC721URIStorageUpgradeable, AccessControlUpgradeabl
33
34
bool _publicMinting;
34
35
string _baseURI;
35
36
string _contractURI;
37
+ mapping (bytes32 nftMetadataHash = > uint256 tokenId ) _nftMetadataHashToTokenId;
36
38
}
37
39
38
40
// keccak256(abi.encode(uint256(keccak256("story-protocol-periphery.SPGNFT")) - 1)) & ~bytes32(uint256(0xff));
@@ -148,6 +150,14 @@ contract SPGNFT is ISPGNFT, ERC721URIStorageUpgradeable, AccessControlUpgradeabl
148
150
return _getSPGNFTStorage ()._contractURI;
149
151
}
150
152
153
+ /// @notice Returns the token ID by the metadata hash.
154
+ /// @dev Returns 0 if the metadata hash has not been used in this collection.
155
+ /// @param nftMetadataHash A bytes32 hash of the NFT's metadata.
156
+ /// @return tokenId The token ID of the NFT with the given metadata hash.
157
+ function getTokenIdByMetadataHash (bytes32 nftMetadataHash ) external view returns (uint256 ) {
158
+ return _getSPGNFTStorage ()._nftMetadataHashToTokenId[nftMetadataHash];
159
+ }
160
+
151
161
/// @notice Sets the fee to mint an NFT from the collection. Payment is in the designated currency.
152
162
/// @dev Only callable by the admin role.
153
163
/// @param fee The new mint fee paid in the mint token.
@@ -207,25 +217,50 @@ contract SPGNFT is ISPGNFT, ERC721URIStorageUpgradeable, AccessControlUpgradeabl
207
217
/// @notice Mints an NFT from the collection. Only callable by the minter role.
208
218
/// @param to The address of the recipient of the minted NFT.
209
219
/// @param nftMetadataURI OPTIONAL. The URI of the desired metadata for the newly minted NFT.
210
- /// @return tokenId The ID of the minted NFT.
211
- function mint (address to , string calldata nftMetadataURI ) public virtual returns (uint256 tokenId ) {
220
+ /// @param nftMetadataHash OPTIONAL. A bytes32 hash of the NFT's metadata.
221
+ /// This metadata is accessible via the NFT's tokenURI.
222
+ /// @param allowDuplicates Set to true to allow minting an NFT with a duplicate metadata hash.
223
+ /// @return tokenId The token ID of the minted NFT with the given metadata hash.
224
+ function mint (
225
+ address to ,
226
+ string calldata nftMetadataURI ,
227
+ bytes32 nftMetadataHash ,
228
+ bool allowDuplicates
229
+ ) public virtual returns (uint256 tokenId ) {
212
230
if (! _getSPGNFTStorage ()._publicMinting && ! hasRole (SPGNFTLib.MINTER_ROLE, msg .sender )) {
213
231
revert Errors.SPGNFT__MintingDenied ();
214
232
}
215
- tokenId = _mintToken ({ to: to, payer: msg .sender , nftMetadataURI: nftMetadataURI });
233
+ tokenId = _mintToken ({
234
+ to: to,
235
+ payer: msg .sender ,
236
+ nftMetadataURI: nftMetadataURI,
237
+ nftMetadataHash: nftMetadataHash,
238
+ allowDuplicates: allowDuplicates
239
+ });
216
240
}
217
241
218
242
/// @notice Mints an NFT from the collection. Only callable by the Periphery contracts.
219
243
/// @param to The address of the recipient of the minted NFT.
220
244
/// @param payer The address of the payer for the mint fee.
221
245
/// @param nftMetadataURI OPTIONAL. The URI of the desired metadata for the newly minted NFT.
222
- /// @return tokenId The ID of the minted NFT.
246
+ /// @param nftMetadataHash OPTIONAL. A bytes32 hash of the NFT's metadata.
247
+ /// This metadata is accessible via the NFT's tokenURI.
248
+ /// @param allowDuplicates Set to true to allow minting an NFT with a duplicate metadata hash.
249
+ /// @return tokenId The token ID of the minted NFT with the given metadata hash.
223
250
function mintByPeriphery (
224
251
address to ,
225
252
address payer ,
226
- string calldata nftMetadataURI
253
+ string calldata nftMetadataURI ,
254
+ bytes32 nftMetadataHash ,
255
+ bool allowDuplicates
227
256
) public virtual onlyPeriphery returns (uint256 tokenId ) {
228
- tokenId = _mintToken ({ to: to, payer: payer, nftMetadataURI: nftMetadataURI });
257
+ tokenId = _mintToken ({
258
+ to: to,
259
+ payer: payer,
260
+ nftMetadataURI: nftMetadataURI,
261
+ nftMetadataHash: nftMetadataHash,
262
+ allowDuplicates: allowDuplicates
263
+ });
229
264
}
230
265
231
266
/// @dev Withdraws the contract's token balance to the fee recipient.
@@ -246,17 +281,40 @@ contract SPGNFT is ISPGNFT, ERC721URIStorageUpgradeable, AccessControlUpgradeabl
246
281
/// @param to The address of the recipient of the minted NFT.
247
282
/// @param payer The address of the payer for the mint fee.
248
283
/// @param nftMetadataURI OPTIONAL. The URI of the desired metadata for the newly minted NFT.
249
- /// @return tokenId The ID of the minted NFT.
250
- function _mintToken (address to , address payer , string calldata nftMetadataURI ) internal returns (uint256 tokenId ) {
284
+ /// @param nftMetadataHash OPTIONAL. A bytes32 hash of the NFT's metadata.
285
+ /// This metadata is accessible via the NFT's tokenURI.
286
+ /// @param allowDuplicates Set to true to allow minting an NFT with a duplicate metadata hash.
287
+ /// @return tokenId The token ID of the minted NFT with the given metadata hash.
288
+ function _mintToken (
289
+ address to ,
290
+ address payer ,
291
+ string calldata nftMetadataURI ,
292
+ bytes32 nftMetadataHash ,
293
+ bool allowDuplicates
294
+ ) internal returns (uint256 tokenId ) {
251
295
SPGNFTStorage storage $ = _getSPGNFTStorage ();
252
296
if (! $._mintOpen) revert Errors.SPGNFT__MintingClosed ();
253
297
if ($._totalSupply + 1 > $._maxSupply) revert Errors.SPGNFT__MaxSupplyReached ();
254
298
299
+ tokenId = $._nftMetadataHashToTokenId[nftMetadataHash];
300
+ if (! allowDuplicates && tokenId != 0 ) {
301
+ revert Errors.SPGNFT__DuplicatedNFTMetadataHash ({
302
+ spgNftContract: address (this ),
303
+ tokenId: tokenId,
304
+ nftMetadataHash: nftMetadataHash
305
+ });
306
+ }
307
+
255
308
if ($._mintFeeToken != address (0 ) && $._mintFee > 0 ) {
256
309
IERC20 ($._mintFeeToken).transferFrom (payer, address (this ), $._mintFee);
257
310
}
258
311
259
312
tokenId = ++ $._totalSupply;
313
+ if ($._nftMetadataHashToTokenId[nftMetadataHash] == 0 ) {
314
+ // only store the token ID if the metadata hash is not used
315
+ $._nftMetadataHashToTokenId[nftMetadataHash] = tokenId;
316
+ }
317
+
260
318
_mint (to, tokenId);
261
319
262
320
if (bytes (nftMetadataURI).length > 0 ) _setTokenURI (tokenId, nftMetadataURI);
0 commit comments