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();
_tokenIdCounter.increment();
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
_totalSupply += 1;
}
// The following functions are overrides required by Solidity.
function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}
function tokenURI(uint256 tokenId)
public
view
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];
saleIndex++;
}
}
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
payable(seller).transfer(msg.value);
// 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.
Node.js:
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:<%=@nft.nft_collection_id%>, nftId:<%=@nft.id%>, invPublicKey:address},
complete: function(jqXHR, textStatus) {
window.location.replace('/application/nfts/<%= @nft.id %>');
}
});
<% end %>
} catch (error) {
$("#loadingEthTransactionModal").modal('hide');
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)],
});
$("#loadingEthTransactionModal").modal('show');
return hash
}
Thank you very much and look forward to your help.