How to accept ERC20 tokens as payment for NFT minting

I’m working on a smart contract that should accept both ETH and ERC20 tokens like USDT for NFT purchases. However, I keep running into compilation issues with my imports. Here’s what I’m trying to build:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts@4.8.0/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts@4.8.0/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts@4.8.0/access/Ownable.sol";
import "@openzeppelin/contracts@4.8.0/utils/Counters.sol";
import "@openzeppelin/contracts@4.8.0/token/ERC20/IERC20.sol";

contract GameCards is ERC721, ERC721Enumerable, Ownable {
    using Counters for Counters.Counter;
    
    Counters.Counter private _cardIdCounter;
    
    IERC20 public paymentToken;
    uint256 public ethCost = 0.01 ether;
    uint256 public tokenCost = 50 * 10**18;
    uint256 public totalCards;
    bool public saleActive;
    
    constructor(address _paymentToken) ERC721("GameCards", "GC") {
        totalCards = 500;
        paymentToken = IERC20(_paymentToken);
    }
    
    function updatePaymentToken(address _newToken) external onlyOwner {
        paymentToken = IERC20(_newToken);
    }
    
    function setCosts(uint256 _ethCost, uint256 _tokenCost) external onlyOwner {
        ethCost = _ethCost;
        tokenCost = _tokenCost;
    }
    
    function purchaseWithToken() external {
        require(paymentToken.balanceOf(msg.sender) >= tokenCost, "Insufficient tokens");
        paymentToken.transferFrom(msg.sender, owner(), tokenCost);
        createCard();
    }
    
    function purchaseWithEth() external payable {
        require(msg.value >= ethCost, "Not enough ETH sent");
        createCard();
    }
    
    function createCard() internal {
        require(saleActive, "Sale not active");
        require(totalSupply() < totalCards, "Max supply reached");
        
        uint256 newCardId = _cardIdCounter.current();
        _cardIdCounter.increment();
        _safeMint(msg.sender, newCardId);
    }
    
    function toggleSale(bool _active) external onlyOwner {
        saleActive = _active;
    }
}

The main issue seems to be with importing the right ERC20 interface. Should I use IERC20 instead of ERC20 for token transfers? What’s the proper way to handle multiple payment methods in a single NFT contract?

Interesting approach! I’m curious though - why go with a single payment token that the owner can update instead of supporting multiple ERC20s at once? Users might get confused if the accepted token changes during a sale.

Also wondering about pricing strategy. You’ve got tokenCost at 50 tokens, but what happens when token prices go crazy compared to ETH? Planning to update costs manually or thinking about oracle integration?

One thing caught my eye - your purchaseWithToken() function transfers tokens straight to owner(), but the ETH version just sits in the contract. Intentional? Might be cleaner to keep both payment types in the contract with a unified withdrawal system.

Quick question - what happens if someone sends ETH directly to the contract without calling purchaseWithEth()? Maybe add a fallback function or at least document that behavior clearly.

yeah, the IERC20 interface looks right, but you’re missing override functions. add override(ERC721, ERC721Enumerable) to _beforeTokenTransfer and supportsInterface - otherwise it won’t compile. also throw in reentrancy guards on your purchase functions to prevent attacks. openzeppelin’s ReentrancyGuard works great for this.

Your contract structure looks solid, but there are some critical issues beyond the imports. IERC20 is the right choice for token transfers - you don’t need the full ERC20 implementation since you’re just interacting with external tokens.

The compilation issue is probably from the versioned import syntax. Try standard imports instead: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; without the version number.

Big security issue: your purchaseWithToken() function doesn’t check approvals. You’re calling transferFrom but users need to approve your contract first. Add a function that checks allowances or at least document the approval requirement clearly.

You’re also missing ETH withdrawal. You’re collecting ETH payments but can’t withdraw them. Add a withdraw function that transfers the contract balance to the owner.

For ERC721Enumerable overrides, implement the required functions like _beforeTokenTransfer, supportsInterface, and tokenURI to fix compilation errors.