How to Create a Solidity Function Allowing Users to Claim One Free ERC721 NFT Each?

I need help building a function that lets anyone claim exactly one free NFT during a promotional event. Right now my code only works when the contract owner calls it, but I want regular users to be able to mint their own free tokens.

Here’s what I have so far:

contract PromoNFTContract is ERC721, ERC721Enumerable, Ownable {

    bool public giveawayEnabled = false;
    uint256 public remainingFreeTokens = 5;
    
    function claimFreeToken(uint qty, address recipient) public onlyOwner {
        require(giveawayEnabled, "Giveaway is not currently active");
        require(qty <= remainingFreeTokens, "Not enough tokens left");
        require(qty > 0, "Quantity must be positive");
        
        for (uint j = 0; j < qty; j++) {
            uint256 newTokenId = totalSupply();
            if (newTokenId < remainingFreeTokens) {
                _safeMint(recipient, newTokenId);
            }
        }
        
        remainingFreeTokens = remainingFreeTokens.sub(qty);
    }
}

The problem is users can’t call this themselves because of the onlyOwner modifier. I want each person to mint one free NFT on their own. Is there a way to track who already claimed theirs? Any ideas would help, even if it involves some off-chain tracking.

remove the onlyOwner modifier and add a mapping to track claims: mapping(address => bool) public hasClaimed;. then check require(!hasClaimed[msg.sender], "already claimed"); and set hasClaimed[msg.sender] = true; after minting. also fix your loop logic - it should mint qty tokens, not check newTokenId < remainingFreeTokens.

Hey @WhisperingTree! Quick question - how are you handling sybil attacks? What stops someone from spinning up multiple wallets and claiming from each?

The mapping approach others mentioned works great for tracking claims, but is that enough protection for what you’re doing? Some projects require holding specific tokens or being whitelisted, though that might be overkill for a simple promo.

Also curious - why make this claimable instead of a traditional airdrop? Trying to dodge gas costs by making users pay their own fees? Pretty smart move if that’s the case since you won’t be sending hundreds of transactions.

One last thing - what happens to unclaimed tokens? Can you withdraw leftovers after the promo ends?

Your main problem is the onlyOwner restriction, but you’ve got other issues too. The loop logic is backwards - you’re checking if newTokenId is less than remainingFreeTokens, which makes no sense for minting. Here’s what I’d do instead: use a mapping like mapping(address => bool) private claimed; to track who’s already minted. Ditch onlyOwner and add require(!claimed[msg.sender], "Already claimed"); at the top. Drop the qty parameter completely since you want one per person anyway. For token IDs, don’t use totalSupply() - create a separate counter like uint256 private nextTokenId = 1; and increment it after each mint. This prevents problems if you add other minting functions or burn tokens later.