Hi,
I'm trying to implement a very simple NFT Marketplace contract.
But on buyNFT operation I cannot get the token to be transferred to the buyer since it's not approved.
I understand that I need to use setApprovalForAll but I've tried many configurations with no luck.
What am I missing here? Any help will be appreciated.
Thanks in advance
Code to reproduce
Contract:
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.9;
import "../openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";
import "../openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol";
import "../openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "../openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
contract ArttsNFT2 is ERC721URIStorage, ERC721Enumerable, IERC721Receiver {
address private minter;
mapping(uint256 => uint256) private _prices;
constructor(string memory _name, string memory _symbol)
ERC721(_name, _symbol)
{
minter = msg.sender;
}
function createNFT(string memory tokenURI, uint256 price)
public
returns (uint256)
{
require(msg.sender == minter, "Only contract creator can mint");
uint256 newItemId = totalSupply() + 1;
_safeMint(minter, newItemId);
_setTokenURI(newItemId, tokenURI);
setApprovalForAll(address(this),true);
_prices[newItemId] = price;
return newItemId;
}
function buyNFT(uint256 tokenId) public payable returns (uint256) {
require(
msg.value >= _prices[tokenId],
"Transaction value is less than item's price"
);
payable(minter).transfer(msg.value);
safeTransferFrom(ownerOf(tokenId), msg.sender, tokenId);
return tokenId;
}
// function safeTransferFrom(address owner, address buyer, uint256 tokenId) override public payable {
// require(
// msg.value >= _prices[tokenId],
// "Transaction value is less than item's price"
// );
// safeTransferFrom(minter, msg.sender, tokenId);
// payable(minter).transfer(msg.value);
// }
function priceOf(uint256 tokenId) public view returns (uint256) {
return _prices[tokenId];
}
// function isApprovedForAll(address owner, address operator)
// public
// view
// override
// returns (bool)
// {
// if (owner == minter) return true;
// else return false;
// }
function onERC721Received(
address _operator,
address _from,
uint256 _tokenId,
bytes calldata _data
) external pure override returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal override(ERC721, ERC721Enumerable) {
super._beforeTokenTransfer(from, to, tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
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);
}
}
Test unit
const nft = artifacts.require("./ArttsNFT2.sol");
var contractInstance
var _name = "NAME"
var _symbol = "SYMBOL"
var _price = ".001"
var _tokenUri = "http://google.com"
contract("ArttsNFT2", accounts => {
const artts_address = accounts[0];
const buyer = accounts[1];
it("...should get an instance to a contract.", async () => {
contractInstance = await nft.new(_name, _symbol)
console.log("Contract Address", contractInstance.address)
});
it("...should create an NFT and minter should be the owner.", async () => {
const result = await contractInstance.createNFT(_tokenUri, web3.utils.toBN(web3.utils.toWei(_price, 'ether')).toString());
assert.equal(await contractInstance.totalSupply(), 1, "There is not 1 token minted in totalSupply");
const owner = await contractInstance.ownerOf(1);
assert.equal(owner,artts_address, "minter account does not own the token");
});
it("...should buy the NFT and minter should receive payment.", async () => {
const balance_before = await web3.eth.getBalance(artts_address)
const response = await contractInstance.buyNFT(1,{
from: buyer,
gasPrice: web3.utils.toWei('5', 'gwei'),
gasLimit: web3.utils.toWei('0.39', 'mwei'),
value: web3.utils.toWei(_price)
})
const owner = await contractInstance.ownerOf(1);
assert.equal(owner, buyer, "Buyer account does not own the token");
const balance_after = await web3.eth.getBalance(artts_address)
assert(balance_after > balance_before, "Minter did not receive any payment");
});
});
Error
Contract: ArttsNFT2
Contract Address 0x1567A26506Ee76F33b1F60EB4d01C3f73b6EF8cE
✓ ...should get an instance to a contract. (351ms)
✓ ...should create an NFT and minter should be the owner. (279ms)
✓ ...should create another NFT and totalSupply() should be 2. (277ms)
✓ ...should create another NFT and totalSupply() should be 3. (280ms)
1) ...should buy the NFT and minter should receive payment.
> No events were emitted
4 passing (3s)
1 failing
1) Contract: ArttsNFT2
...should buy the NFT and minter should receive payment.:
Error: Returned error: VM Exception while processing transaction: revert ERC721: transfer caller is not owner nor approved -- Reason given: ERC721: transfer caller is not owner nor approved.
at Context.<anonymous> (test/ArttsNFT2.js:48:45)
at runMicrotasks (<anonymous>)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
Environment
truffle(develop)> version
Truffle v5.4.15 (core: 5.4.15)
Solidity - ^0.8.9 (solc-js)
Node v12.22.7
Web3.js v1.5.3