Allowance error when using transferFrom in ERC20 payment system for NFT minting

Issue with Token Payment for NFT Minting

I’m working with two contracts - an ERC1155 NFT contract and an ERC20 token contract. Users should be able to buy NFTs using my custom ERC20 tokens, but I keep getting this error: ERC20: transfer amount exceeds allowance when calling transferFrom().

My NFT Contract (ERC1155):

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract GameItemNFT is ERC1155, Ownable {
    IERC20 private _paymentToken;

    constructor(IERC20 paymentToken)
        ERC1155(
            "ipfs://QmSampleHash123/metadata/{id}.json"
        )
    {
        _paymentToken = paymentToken;
    }

    function purchaseItem(
        address buyer,
        uint256 tokenId,
        uint256 quantity
    ) public {
        require(_paymentToken.transferFrom(msg.sender, address(this), 1));
        _mint(buyer, tokenId, quantity, "");
    }
}

My ERC20 Token Contract:

// SPDX-License-Identifier: MIT

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

pragma solidity ^0.8.0;

contract GameToken is ERC20, Ownable {
    uint256 public totalMaxSupply = 50000 * 10**18;

    constructor() ERC20("Game Coin", "GAME") {
        _mint(msg.sender, totalMaxSupply);
    }

    function rewardPlayer(
        address player,
        uint256 tokens,
        bool taskCompleted
    ) public onlyOwner {
        require(taskCompleted == true);
        _mint(player, tokens);
    }

    function setNFTApproval(address _nftContract) public onlyOwner {
        approve(_nftContract, totalMaxSupply);
    }
}

Test Code:

def test_nft_purchase_with_tokens():
    # Setup
    owner = get_account()
    buyer = get_account(1)
    
    game_token = deploy_token_contract()
    nft_contract = GameItemNFT.deploy(game_token, {"from": owner})
    
    # Give tokens to buyer
    reward_tx = game_token.rewardPlayer(buyer, 20, True)
    reward_tx.wait(1)
    
    print(f"Buyer balance: {game_token.balanceOf(buyer.address)}")
    
    # Approve spending
    approve_tx = game_token.approve(buyer.address, game_token.balanceOf(buyer.address))
    approve_tx.wait(1)
    
    # Try to mint NFT
    assert nft_contract.purchaseItem(buyer.address, 15, 1, {"from": buyer})
    print(f"NFT minted successfully")

What am I doing wrong here? Should the minting function be marked as payable?

your test code has the wrong approval. when you do game_token.approve(buyer.address, amount) from the owner account, you’re letting the buyer spend the owner’s tokens - not the buyer’s own tokens. the buyer needs to approve the nft contract first: game_token.approve(nft_contract.address, amount, {"from": buyer}) before calling purchaseItem.

Oh interesting, I see what CreativePainter45 means about the approval issue, but something else caught my eye - why use setNFTApproval in your ERC20 contract? That function just approves the owner to spend tokens on behalf of the contract, which doesn’t help with individual user purchases.

Also, your purchaseItem function hardcodes the transfer amount to 1 token regardless of quantity or tokenId. Is that intentional? Different NFTs probably have different prices, right?

Have you checked the actual allowance right before the transferFrom call? Try adding require(_paymentToken.allowance(msg.sender, address(this)) >= expectedAmount, "insufficient allowance"); for a clearer error message.

One more thing - when you run the test, is the buyer account actually calling purchaseItem? The transferFrom pulls tokens from msg.sender, so if the wrong account calls it, it’ll fail even with proper approvals.

Your test setup’s the problem. You’re calling purchaseItem with {"from": buyer} but doing the approval from the owner account instead of the buyer. When transferFrom runs, it tries pulling tokens from msg.sender (the buyer), but the buyer never approved the NFT contract to spend their tokens.

Fix your approval line: approve_tx = game_token.approve(nft_contract.address, amount, {"from": buyer}). This lets the NFT contract spend the buyer’s tokens, not the owner’s.

Also, your ERC20 contract mints extra tokens in rewardPlayer without checking if it exceeds totalMaxSupply. You set a max supply but don’t enforce it. Add require(totalSupply() + tokens <= totalMaxSupply) to that function so you don’t accidentally over-mint.