Skip to content

Instantly share code, notes, and snippets.

@LeoHChen
Created September 22, 2024 23:15
Show Gist options
  • Select an option

  • Save LeoHChen/d9547fb4b12c970233c7380a263b53ab to your computer and use it in GitHub Desktop.

Select an option

Save LeoHChen/d9547fb4b12c970233c7380a263b53ab to your computer and use it in GitHub Desktop.
StoryBadgeNFT
// 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");
_;
}
}
@LeoHChen
Copy link
Author

LeoHChen commented Sep 22, 2024

// External Minting Hook Contract Example

contract MintingHook {

    function canMint(address minter) external view returns (bool) {
        // Custom logic to determine if the minter is allowed to mint
        return true;  // For simplicity, always return true in this example
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment