Created
September 22, 2024 23:15
-
-
Save LeoHChen/d9547fb4b12c970233c7380a263b53ab to your computer and use it in GitHub Desktop.
StoryBadgeNFT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: MIT | |
| pragma solidity ^0.8.0; | |
| import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | |
| import "@openzeppelin/contracts/access/Ownable.sol"; | |
| contract BadgeNFT is ERC721, Ownable { | |
| uint256 public supplyLimit; | |
| uint256 public totalMinted; | |
| bool public soulBound; | |
| string public baseTokenURI; | |
| address public externalMintHook; // Address of the external hook contract (optional) | |
| mapping(address => bool) public hasMinted; | |
| mapping(address => uint256) public reservedTokens; // Mapping for whitelisted pre-mint reservations | |
| event Minted(address minter, uint256 tokenId); | |
| event ExternalHookUpdated(address hook); | |
| event WhitelistReserved(address indexed user, uint256 tokenId); | |
| event WhitelistClaimed(address indexed user, uint256 tokenId); | |
| constructor( | |
| string memory name, | |
| string memory symbol, | |
| uint256 _supplyLimit, | |
| string memory _baseURI, | |
| address _owner | |
| ) | |
| ERC721(name, symbol) | |
| { | |
| supplyLimit = _supplyLimit; | |
| baseTokenURI = _baseURI; | |
| soulBound = true; | |
| transferOwnership(_owner); | |
| } | |
| // External hook check (if a hook is set by the owner) | |
| modifier externalHookCheck() { | |
| if (externalMintHook != address(0)) { | |
| (bool success, bytes memory data) = externalMintHook.call(abi.encodeWithSignature("canMint(address)", msg.sender)); | |
| require(success && abi.decode(data, (bool)), "External minting hook failed"); | |
| } | |
| _; | |
| } | |
| // General mint function for non-whitelisted users | |
| function mint() public externalHookCheck { | |
| require(totalMinted < supplyLimit, "Max supply reached"); | |
| require(!hasMinted[msg.sender], "Already minted"); | |
| totalMinted++; | |
| uint256 tokenId = totalMinted; | |
| hasMinted[msg.sender] = true; | |
| _safeMint(msg.sender, tokenId); | |
| emit Minted(msg.sender, tokenId); | |
| } | |
| // Claim reserved NFT for whitelisted users | |
| function claimReserved() public externalHookCheck { | |
| require(reservedTokens[msg.sender] > 0, "No reserved token for this address"); | |
| require(!hasMinted[msg.sender], "Already claimed"); | |
| uint256 tokenId = reservedTokens[msg.sender]; | |
| hasMinted[msg.sender] = true; | |
| _safeMint(msg.sender, tokenId); | |
| emit WhitelistClaimed(msg.sender, tokenId); | |
| } | |
| // Owner reserves tokens for whitelisted users | |
| function reserveTokensForWhitelist(address[] memory addresses) public onlyOwner { | |
| require(totalMinted + addresses.length <= supplyLimit, "Not enough supply left"); | |
| for (uint256 i = 0; i < addresses.length; i++) { | |
| require(reservedTokens[addresses[i]] == 0, "Address already has a reserved token"); | |
| totalMinted++; | |
| reservedTokens[addresses[i]] = totalMinted; | |
| emit WhitelistReserved(addresses[i], totalMinted); | |
| } | |
| } | |
| // Set an external hook contract to add custom minting logic | |
| function setExternalMintHook(address hook) public onlyOwner { | |
| externalMintHook = hook; | |
| emit ExternalHookUpdated(hook); | |
| } | |
| // Override tokenURI to provide the correct metadata URI | |
| function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { | |
| return string(abi.encodePacked(baseTokenURI, "/", Strings.toString(tokenId), ".json")); | |
| } | |
| // Restrict transfers since this is a soul-bound token | |
| function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override { | |
| require(from == address(0), "This token is soul-bound and cannot be transferred"); | |
| super._beforeTokenTransfer(from, to, tokenId); | |
| } | |
| // Prevent smart contracts from minting | |
| function _isEOA() internal view returns (bool) { | |
| return msg.sender == tx.origin; | |
| } | |
| modifier onlyEOA() { | |
| require(_isEOA(), "Contracts cannot mint"); | |
| _; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.