"Error: VM Exception while processing transaction: reverted with reason string 'ERC20: insufficient allowance'"

Hi everyone!
I try create NFT marketplace, where i can buy NFT with erc20 token.

MyMarket.sol

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

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
// security against transactions for multiple requests
import "hardhat/console.sol";
import "./OurToken.sol";
import "./NFT.sol";

contract KBMarket is ReentrancyGuard {
    using Counters for Counters.Counter;

    OurToken private ourtoken;

    /* number of items minting, number of transactions, tokens that have not been sold
     keep track of tokens total number - tokenId
     arrays need to know the length - help to keep track for arrays */

    Counters.Counter private _tokenIds;
    Counters.Counter private _tokensSold;

    // determine who is the owner of the contract
    // charge a listing fee so the owner makes a commission

    address payable owner;
    // we are deploying to matic the API is the same so you can use ether the same as matic
    // they both have 18 decimal
    // 0.045 is in the cents
    uint256 listingPrice = 0.045 ether;

    modifier isMyToken(address _token) {
        require(_token == address(ourtoken), "Not my token");
        _;
    }

    constructor(address tokenaddress) {
        //set the owner
        owner = payable(msg.sender);
        ourtoken = OurToken(tokenaddress);
    }

    // structs can act like objects

    struct MarketToken {
        uint256 itemId;
        address nftContract;
        uint256 tokenId;
        address payable seller;
        address payable owner;
        uint256 price;
        bool sold;
    }

    // tokenId return which MarketToken -  fetch which one it is

    mapping(uint256 => MarketToken) private idToMarketToken;

    // listen to events from front end applications
    event MarketTokenMinted(
        uint256 indexed itemId,
        address indexed nftContract,
        uint256 indexed tokenId,
        address seller,
        address owner,
        uint256 price,
        bool sold
    );

    // get the listing price
    function getListingPrice() public view returns (uint256) {
        return listingPrice;
    }

    // two functions to interact with contract
    // 1. create a market item to put it up for sale
    // 2. create a market sale for buying and selling between parties

    function makeMarketItem(
        address nftContract,
        uint256 tokenId,
        uint256 price
    ) public payable nonReentrant {
        // nonReentrant is a modifier to prevent reentry attack

        require(price > 0, "Price must be at least one wei");
        require(
            msg.value == listingPrice,
            "Price must be equal to listing price"
        );

        _tokenIds.increment();
        uint256 itemId = _tokenIds.current();

        //putting it up for sale - bool - no owner
        idToMarketToken[itemId] = MarketToken(
            itemId,
            nftContract,
            tokenId,
            payable(msg.sender),
            payable(address(0)),
            price,
            false
        );

        // NFT transaction
        IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);

        emit MarketTokenMinted(
            itemId,
            nftContract,
            tokenId,
            msg.sender,
            address(0),
            price,
            false
        );
    }

    // function to conduct transactions and market sales

    function createMarketSale(address nftContract, uint256 itemId)
        public
        nonReentrant
    {
        uint256 price = idToMarketToken[itemId].price;
        uint256 value = price * (10**ERC20(ourtoken).decimals());
        uint256 tokenId = idToMarketToken[itemId].tokenId;

        // transfer the amount to the seller
        ERC20(ourtoken).transferFrom(msg.sender, address(this), value);
        IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId);
        _tokensSold.increment();

        //payable(owner).transfer(listingPrice);
    }

    // function to fetchMarketItems - minting, buying ans selling
    // return the number of unsold items

    function fetchMarketTokens() public view returns (MarketToken[] memory) {
        uint256 itemCount = _tokenIds.current();
        uint256 unsoldItemCount = _tokenIds.current() - _tokensSold.current();
        uint256 currentIndex = 0;

        // looping over the number of items created (if number has not been sold populate the array)
        MarketToken[] memory items = new MarketToken[](unsoldItemCount);
        for (uint256 i = 0; i < itemCount; i++) {
            if (idToMarketToken[i + 1].owner == address(0)) {
                uint256 currentId = i + 1;
                MarketToken storage currentItem = idToMarketToken[currentId];
                items[currentIndex] = currentItem;
                currentIndex += 1;
            }
        }
        return items;
    }

    // return nfts that the user has purchased

    function fetchMyNFTs() public view returns (MarketToken[] memory) {
        uint256 totalItemCount = _tokenIds.current();
        // a second counter for each individual user
        uint256 itemCount = 0;
        uint256 currentIndex = 0;

        for (uint256 i = 0; i < totalItemCount; i++) {
            if (idToMarketToken[i + 1].owner == msg.sender) {
                itemCount += 1;
            }
        }

        // second loop to loop through the amount you have purchased with itemcount
        // check to see if the owner address is equal to msg.sender

        MarketToken[] memory items = new MarketToken[](itemCount);
        for (uint256 i = 0; i < totalItemCount; i++) {
            if (idToMarketToken[i + 1].owner == msg.sender) {
                uint256 currentId = idToMarketToken[i + 1].itemId;
                // current array
                MarketToken storage currentItem = idToMarketToken[currentId];
                items[currentIndex] = currentItem;
                currentIndex += 1;
            }
        }
        return items;
    }

    // function for returning an array of minted nfts
    function fetchItemsCreated() public view returns (MarketToken[] memory) {
        // instead of .owner it will be the .seller
        uint256 totalItemCount = _tokenIds.current();
        uint256 itemCount = 0;
        uint256 currentIndex = 0;

        for (uint256 i = 0; i < totalItemCount; i++) {
            if (idToMarketToken[i + 1].seller == msg.sender) {
                itemCount += 1;
            }
        }

        // second loop to loop through the amount you have purchased with itemcount
        // check to see if the owner address is equal to msg.sender

        MarketToken[] memory items = new MarketToken[](itemCount);
        for (uint256 i = 0; i < totalItemCount; i++) {
            if (idToMarketToken[i + 1].seller == msg.sender) {
                uint256 currentId = idToMarketToken[i + 1].itemId;
                MarketToken storage currentItem = idToMarketToken[currentId];
                items[currentIndex] = currentItem;
                currentIndex += 1;
            }
        }
        return items;
    }
}

NFT.sol

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

// we will bring in the openzeppelin ERC721 NFT functionality

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract NFT is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;
    // counters allow us to keep track of tokenIds

    // address of marketplace for NFTs to interact
    address contractAddress;

    // OBJ: give the NFT market the ability to transact with tokens or change ownership
    // setApprovalForAll allows us to do that with contract address

    // constructor set up our address
    constructor(address marketplaceAddress) ERC721("KryptoBirdz", "KBIRDZ") {
        contractAddress = marketplaceAddress;
    }

    function mintToken(string memory tokenURI) public returns (uint256) {
        _tokenIds.increment();
        uint256 newItemId = _tokenIds.current();
        _mint(msg.sender, newItemId);
        // set the token URI: id and url
        _setTokenURI(newItemId, tokenURI);
        // give the marketplace the approval to transact between users
        setApprovalForAll(contractAddress, true);
        // mint the token and set it for sale - return the id to do so
        return newItemId;
    }
}

Token.sol

// contracts/OurToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

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

contract OurToken is ERC20 {
    constructor() ERC20("Liver", "LIV") {
        _mint(0xbDA5747bFD65F08deb54cb465eB87D40e51B197E, 1000000E18);
    }
}

deploy.js


const hre = require("hardhat");
const fs = require('fs')

async function main() {

  const Token = await hre.ethers.getContractFactory("OurToken");
  const token = await Token.deploy();
  await token.deployed();
  console.log("token contract deployed to: ", token.address);

  const NFTMarket = await hre.ethers.getContractFactory("KBMarket");
  const nftMarket = await NFTMarket.deploy(token.address);
  await nftMarket.deployed();
  console.log("nftMarket contract deployed to: ", nftMarket.address);

  const NFT = await hre.ethers.getContractFactory("NFT");
  const nft = await NFT.deploy(nftMarket.address);
  await nft.deployed();
  console.log("NFT contract deployed to: ", nftMarket.address);


  let config = `
  export const nftmarketaddress = ${nftMarket.address}
  export const nftaddress = ${nft.address}
  export const tokenaddress = ${token.address}`

  let data = JSON.stringify(config)
  fs.writeFileSync('config.js', JSON.parse(data))

}


main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

function byu nft on my index.js

async function buyNFT(nft) {
    try {
      const web3Modal = new Web3Modal()
      const connection = await web3Modal.connect()
      const provider = new providers.Web3Provider(window.ethereum)
      await provider.send("eth_requestAccounts", []);
      const signer = provider.getSigner()
      console.log(signer)
      const contract = new ethers.Contract(nftmarketaddress, KBMarket.abi, signer)

      const price = ethers.utils.parseUnits(nft.price.toString(), 'ether')
      const token = new ethers.Contract(tokenaddress, OurToken.abi, signer)
      await token.approve(nftmarketaddress, ethers.utils.parseEther(nft.price.toString()))
      console.log(nft.price.toString())
      const signerAddress = await signer.getAddress()
      console.log(signerAddress)
      console.log((await token.balanceOf(signerAddress)).toString())
      const transaction = await contract.createMarketSale(nftaddress, nft.tokenId)

      await transaction.wait()
      loadNFTs()
    } catch (err) { console.log(err) }

  }

When i click buy on my site, i have error on my console:
MetaMask - RPC Error: Internal JSON-RPC error
message: "Error: VM Exception while processing transaction: reverted with reason string 'ERC20: insufficient allowance'"

I will be grateful for your help

As the error tells there is not enough allowance. For a contract to be able to move around assests of an user he needs approval. Let's say the user buys an NFT for 10USDC. Before buying the user needs to go to the contract of USDC and use the approve function (spender: your contract, amount: 10*1e18).

You have to use the increaseallowance function called to the token contract, the allowance is usually zero
You have to increase a token allowance before it can be transferred

hello, how i can do this? In my MyMarket.sol ?

You have to call it from your frontend to the token contract

ping me via telegram
@skyhdev

can you show example plz?

like this ?

await token.increaseAllowance(nftmarketaddress, ethers.utils.parseEther(nft.price.toString()))

Yes, just like that man