ERC721: caller is not token owner or approved

Hi!
Can someone help me figure out what is missing with the contract below
the functions mint, listToken, buyNFT work fine

Although, when it comes to putUpForSale, the transaction keeps reverting with the error :
ERC721: caller is not token owner or approved

I am guessing the error happens when the ERC721 internal function is called _isApprovedOrOwner(address spender, uint256 tokenId)

  • How come though ?
    spender == owner should return true. the msg.sender is the token' owner and spender is _msgSender (= msg.sender)

I have been using to test locally on VM
I have not had much luck with the debug feature.

the file below with 2 smart contracts : NFT and NFTMarketplace


    //SPDX-License-Identifier: Unlicense
    pragma solidity ^0.8.0;
    
    import "@openzeppelin/contracts/utils/Counters.sol";
    import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
    import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
    
    contract NFT is ERC721URIStorage {
        using Counters for Counters.Counter;
        Counters.Counter private _tokenIds;
        address marketplaceContract;
        event NFTMinted(uint256 tokenId);
    
        constructor(address _marketplaceContract) ERC721("NFTMarketplace", "NFTM") {
            marketplaceContract = _marketplaceContract;
        }
    
        function mint(string memory _tokenURI) public {
            uint256 currentTokenId = _tokenIds.current();
            _tokenIds.increment(); 
            _safeMint(msg.sender, currentTokenId);
            _setTokenURI(currentTokenId, _tokenURI);
            setApprovalForAll(marketplaceContract, true);
    
            emit NFTMinted(currentTokenId);
        }
    
        function approveUser(address _user) public {
            setApprovalForAll(_user, true);
        }
    
        function getCurrentToken() public view returns (uint256) {
            return _tokenIds.current();
        }
    }
    
    contract NFTMarketplace is ReentrancyGuard {
    
        using Counters for Counters.Counter;
        Counters.Counter private _itemsSold;
        Counters.Counter private _itemsCount;
        uint256 public LISTING_FEE = 0.01 ether;
        address payable private _marketOwner;
        mapping(uint256 => Token) private _idToToken;
    
        constructor() {
            _marketOwner = payable(msg.sender);
        }
    
        //The structure to store info about a listed token
        struct Token {
            address nftContract;
            uint256 tokenId;
            address payable seller;
            address payable owner;
            string tokenURI;
            string name;
            uint256 price;
            bool isListed;
        }
    
        event TokenListed(
            address nftContract,
            uint256 tokenId,
            address seller,
            address owner,
            uint256 price
        );
        event TokenSold(
            address nftContract,
            uint256 tokenId,
            address seller,
            address owner,
            uint256 price
        );
    
        //the event emitted when a token is successfully listed
        event TokenListedSuccess (
            uint256 indexed tokenId,
            address nftContract,
            address owner,
            uint256 price,
            bool isListed
        );
    
    
    
        //The first time a token is created, it is listed here
        // make it payable with money -- add require
        function listToken(address _nftContract, uint256 _tokenId, string memory _tokenURI, string memory name) public  {
    
            // require(msg.value > 0, "Price must be at least 1 wei");
            // require(msg.value == LISTING_FEE, "Price must be equal to listing price");
          
            IERC721(_nftContract).transferFrom(msg.sender, address(this), _tokenId);
    
            _idToToken[_tokenId] = Token(
                _nftContract,
                _tokenId,
                payable(msg.sender),
                payable(address(this)),
                _tokenURI,
                name,
                LISTING_FEE, 
                true
            );
    
            _itemsCount.increment();
            emit TokenListed(
                _nftContract,
                _tokenId,
                msg.sender,
                address(this),
                LISTING_FEE
            );
        }
    
        function buyNFT(address _nftContract, uint256 _tokenId) public payable nonReentrant {
            Token storage token = _idToToken[_tokenId];
            address payable buyer = payable(msg.sender);
            address payable seller = payable(token.seller);
    
            require(msg.value > 0, "You need to send some ether");
            require(msg.value >= token.price, "Not enough ether to cover asking price");
            // require(buyer != seller,"You already own this nft");
            
            IERC721(_nftContract).approve(msg.sender, _tokenId);
            IERC721(_nftContract).transferFrom(address(this), buyer, _tokenId);
          
    
            payable(seller).transfer(msg.value);
            _itemsSold.increment();
            token.isListed = false;
    
            emit TokenSold(_nftContract, token.tokenId, address(this), msg.sender, msg.value);
    
            token.seller = token.owner;
            token.owner = payable(address(this));
        }
    
        function putUpForSale(address _nftContract, uint256 _tokenId) public payable {
            address owner = ERC721(_nftContract).ownerOf(_tokenId);
            require(msg.value > 0, "Price must be at least 1 wei");
            require(msg.value == LISTING_FEE, "Not enough ether for listing fee");
            require(owner == msg.sender, "you are not the owner of the token");
        
            IERC721(_nftContract).safeTransferFrom(owner, address(this), _tokenId);
    
            payable(address(this)).transfer(msg.value);
        
            Token storage token = _idToToken[_tokenId];
            token.seller = payable(msg.sender);
            token.owner = payable(address(this));
            token.isListed = true;
        
            _itemsSold.decrement();
            emit TokenListed(_nftContract, _tokenId, msg.sender, address(this), LISTING_FEE);
        }
    
        function getListPrice() public view returns (uint256) {
            return LISTING_FEE;
        }
        function getLatestIdToToken(uint256 _tokenId) public view returns (Token memory) {
            return _idToToken[_tokenId];
        }
        function getTokenForId(uint256 _tokenId) public view returns (Token memory) {
            return _idToToken[_tokenId];
        }
    
         function getOwnerOf(address _nftContract, uint256 _tokenId) public view returns(address) {
            return IERC721(_nftContract).ownerOf(_tokenId);
        }
       
        function getMarketPlaceBalance() public view returns(uint256) {
            //https://docs.soliditylang.org/en/develop/units-and-global-variables.html#address-related
            return address(this).balance;
        }
    
        function getItemsCount() public view returns(uint256) {
            return _itemsCount.current();
        }
        function getItemsSold() public view returns(uint256) {
            return _itemsSold.current();
        }
    
        //We might add a resell token function in the future
        //In that case, tokens won't be listed by default but users can send a request to actually list a token
        //Currently NFTs are listed by default
    }

additional information

the transaction to sell the token and transfer to the market place is made by the owner

Screenshot 2022-10-06 at 11.10.19
Screenshot 2022-10-06 at 11.10.34

so, I do not quite understand the error =>

The transaction has been reverted to the initial state.
Reason provided by the contract: "ERC721: caller is not token owner nor approved".
Debug the transaction to get more information.

same result and error with this marketplace example => https://trufflesuite.com/guides/nft-marketplace/

The owner needs to permit the contract from transferring the token.