I hope you are doing well? I am new to the world of web3 and to this forum and I hope to find some help in solving my problem.

I create an ERC721 contract that would initially allow me to be able to sell and buy NFTs on my application.

Here is my ERC721 contract:

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

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

// Déclaration contract
contract NFTMarketplace is ERC721, ERC721URIStorage, Ownable {
     using Counters for Counters.Counter;

     Counters.Counter private _tokenIdCounter;
     uint256 public _totalSupply = 0;
     // Structure informations sells
     struct Sale {
         address seller;
         uint256 tokenId;
         uint256 price;
         bool active;
     // Associative array to store current sales
     mapping(uint256 => Sale) private sales;
    event SaleCreated(address indexed seller, uint256 indexed tokenId, uint256 price);
    event SaleEnded(address indexed buyer, uint256 indexed tokenId);
    // Contract ERC721 NFT

    constructor(string memory name,string memory symbol) ERC721(name, symbol) {}

    function safeMint(address to, string memory uri) public onlyOwner {
        //require(nftContract.ownerOf(_tokenId) == msg.sender, "Vous n'etes pas le proprietaire du NFT.");
        uint256 tokenId = _tokenIdCounter.current();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
        _totalSupply += 1;

    // The following functions are overrides required by Solidity.

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {

    function tokenURI(uint256 tokenId)
         override(ERC721, ERC721URIStorage)
         returns (string memory)
         return super.tokenURI(tokenId);

    // Fonction pour mettre un NFT en vente
    // Function to put an NFT on sale
    function createSale( uint256 _tokenId, uint256 _price) public onlyOwner{

        approve(address(this), _tokenId);
        // Transfert du NFT au contrat
        // Transfer of the NFT to the contract
        // transferFrom(msg.sender, address(this), _tokenId);
        // Storage informations 
        Sale memory sale = Sale({
            seller: msg.sender,
            tokenId: _tokenId,
            price: _price,
            active: true
        sales[_tokenId] = sale;

        // Émit event
        emit SaleCreated(msg.sender, _tokenId, _price);
    //numbers of sales
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    // Function to get sells
    function getSales() public view returns (Sale[] memory) {
        Sale[] memory salesList = new Sale[](totalSupply());
        uint256 saleIndex = 0;
        for (uint256 i = 0; i < totalSupply(); i++) {
            if (sales[i].active) {
                salesList[saleIndex] = sales[i];
        return salesList;

    // Function to buy a NFT
    function buy(uint256 _tokenId) external payable {
         Sale storage sale = sales[_tokenId];
         address payable seller = payable(sale.seller);
         address payable buyer = payable(msg.sender);
         require(sale.active, "This sale is no longer active");
         require(msg.value >= sale.price, "The amount sent is less than the sale price");
         IERC721(address(this)).setApprovalForAll(buyer, true);
         IERC721(address(this)).approve(buyer, _tokenId);
         // Transfer NFt at Buyer
         IERC721(address(this)).transferFrom(seller, msg.sender, _tokenId);
         //transfer amount to seller
         // update information sells storage
         sale.active = false;
         // Émit event
         emit SaleEnded(buyer, _tokenId);

I obviously do not encounter any particular problems for the 'createSale' function but for the 'buy' function.

I think I'm doing everything fine, however when I try to buy from a user other than the creator, I always get this error in polygonscan: "Fail with error 'ERC721: approve to caller'"

and in metamask I have this: We could not estimate the price of fuel. Therefore, there may be an error in the contract and this transaction may fail. I still want to continue

Still, I think I'm doing everything right.

If it can help you here is the part of my nodeJs server which will create a payload which will be sent back to my rails application which will take care of displaying the metamask to confirm the transaction.


async function buy(contract, token, buyerKeyPublic, price) {

    let nonce = await web3.eth.getTransactionCount(buyerKeyPublic);
    let gasP = await web3.eth.getGasPrice();

    const data = contract.methods.buy(token).encodeABI();

    const txParams = {
       from: buyerKeyPublic,
       nonce: web3.utils.toHex(nonce),
       gasPrice:web3.utils.toHex( gasP),
       gasLimit: web3.utils.toHex(2000000),
       to: contract.options.address,
       value: web3.utils.toHex(value),
       data: data,
       chainId: web3.utils.toHex(chainId)

   return txParams;

rails - buy.js.erb:

// Check if MetaMask is installed and available
if (typeof window.ethereum !== 'undefined') {
    let params = '<%= @payload.to_json.html_safe %>';

    // Get the user's Ethereum account
    ethereum.request({ method: 'eth_requestAccounts' }).then(async (accounts) => {
        const address = accounts[0];

        try {
             let hash = await createTransaction(params);
             <% if @callback.present? %>
                 console.log("callback PRESENT");
                 $.ajax({ url: "<%= @callback %>",
                     type: 'POST',
                     beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
                     data: {hash:hash,nftIdColl:<%[email protected]_collection_id%>, nftId:<%[email protected]%>, invPublicKey:address},
                     complete: function(jqXHR, textStatus) {
                          window.location.replace('/application/nfts/<%= @nft.id %>');
            <% end %>
        } catch (error) {
             alert("flash eth contracts metamask error or cancelled");

    }).catch(function(error) {
        // MetaMask not installed
        alert('Please install MetaMask to approve this NFT purchase.');
} else {
    // MetaMask not installed
    alert('Please install MetaMask to approve this NFT purchase.');

async function createTransaction(params) {
    let hash = await ethereum.request({
        method: 'eth_sendTransaction',
        params: [JSON.parse(params)],

    return hash

Thank you very much and look forward to your help.

1 Answer 1


Actually the issue is that your NFTMarketplace contract and your ERC721 contract are the same contract, there is some confusion I think. When you use this code:

IERC721(address(this)).setApprovalForAll(buyer, true);

You're actually telling the NFT Marketplace to approve the buyer, but the NFTMarketplace doesn't hold any NFTs.

Same problem here:

IERC721(address(this)).approve(buyer, _tokenId);

And here, you're trying to transfer the NFT from the seller to the buyer:

IERC721(address(this)).transferFrom(seller, msg.sender, _tokenId);

But you need to ensure the seller gives TO the NFTMarketplace the permission to move the NFT, not the contract itself giving the permission (since the NFTMarketplace doesn't have the token).

To fix this, the seller should approve the NFT Marketplace contract to manage their NFT instead of approving it within the contract.

I'm not sure what you're trying to do, but if you want to swap NFTs, both need to approve the contract and then you can transferFrom.

  • ty for your answer :). Something like this: approve(address(this), _tokenId); approve(buyer, _tokenId); // Transfer NFt at Buyer this.transferFrom(seller, msg.sender, _tokenId); ? (msg.sender and buyer are same) Unfortunately, I still have the same 2 errors with these modifications
    – H F
    Commented Mar 23, 2023 at 7:43
  • The approve function needs to be called by users directly and not within the buy function itself.
    – Adam Boudj
    Commented Mar 23, 2023 at 7:57
  • How Can i do that ? In the createsale fonction , i have to add : approve(adresse(this), tokenId)?
    – H F
    Commented Mar 23, 2023 at 8:00
  • No, both seller and buyer must use "approve" function first. This function is part of ERC721 and is outside the main function. It's similar to when you're giving permission to Uniswap before swapping ERC20 tokens. UserA and UserB must do this: UserA says approve(contractAddress, tokenId) UserB says approve(contractAddress, tokenId) (I'm not sure about your exact scenario) After that, you can use the buy function because both users gave permission. Don't put "approve" inside another function. If you do, the contract, not the user, gives permission (msg.sender changes).
    – Adam Boudj
    Commented Mar 23, 2023 at 8:43
  • Thank you very much !!! i have to approve seller and know my function buy works correctly !!!!! you saved me <3
    – H F
    Commented Mar 24, 2023 at 9:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.