I am building an NFT marketplace where the user actions are as follows.
- Address A uploads an image to create an NFT. This action calls smart contract functions createToken() to create ERC721 token and listNFTItem() to list the token.
- Now, if Address B wants to buy this token, this action is executed by calling Buy() that in turn calls a transferFrom() within it.
- Now ideally ownership of the token should be transferred to Address B from Address A and Address B should be able relist it.
- Here is where I am facing the problem. When Address B is relisting this token using relistNftItem() that calls transferFrom(), this action fails with an error 'ERC721: transfer caller is not owner nor approved'
Now after researching for a bit in stackoverflow and reading openzeppelin contracts, I realise that this error is a fairly common and 'Address B' just needs to approve the 'to' address(which is a contract address in this case) before relisting. But adding an approve(address(this), tokenId) in relistNftItem() function raises this new error 'ERC721: approve caller is not owner nor approved for all'. The error is same even when I replace address(this) with msg.sender in approve().
Here is the createToken()
function createToken(string memory tokenURI) public returns (uint256) {
// Token URI contains metadata like msg.sender etc
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(msg.sender, newItemId);
_setTokenURI(newItemId, tokenURI);
// Giving approval to the contract address for transferring ownership from all nfts
setApprovalForAll(contractAddress, true);
return newItemId;
}
Here is my listNFTItem() function on smart contract
function listNFTItem(
address nftContract,
uint256 tokenId,
uint256 price
) public payable nonReentrant {
require(price > 0, "Price must be at least 1 wei");
require(
msg.value == listingPrice,
"Price must be equal to listing price"
);
_itemIds.increment();
uint256 itemId = _itemIds.current();
idToMarketItem[itemId] = MarketItem(
itemId,
nftContract,
tokenId,
payable(msg.sender),
payable(address(0)),
price,
false
);
IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);
emit MarketItemCreated(
itemId,
nftContract,
tokenId,
msg.sender,
address(0),
price,
false
);
}
Here is the Buy()
function Buy(address nftContract, uint256 itemId)
public
payable
nonReentrant
{
uint256 price = idToMarketItem[itemId].price;
uint256 tokenId = idToMarketItem[itemId].tokenId;
require(
msg.value == price,
"Please submit the asking price in order to complete the purchase"
);
idToMarketItem[itemId].seller.transfer(msg.value);
IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId);
idToMarketItem[itemId].owner = payable(msg.sender);
idToMarketItem[itemId].sold = true;
_itemsSold.increment();
payable(owner).transfer(listingPrice);
}
Here is the contract code for relistNFTitem()
function relistNFTitem(address nftContract, uint256 itemId)
public
payable
nonReentrant
{
uint256 price = idToMarketItem[itemId].price;
uint256 tokenId = idToMarketItem[itemId].tokenId;
require(
msg.value == price,
"Please submit the asking price in order to complete the purchase"
);
require(
idToMarketItem[itemId].owner == msg.sender,
"Only token owner can put up token for sell"
);
idToMarketItem[itemId].seller.transfer(msg.value);
// I am approving the approve contract here. Commented it out to reproduce step by step.
// IERC721(nftContract).approve(address(this), tokenId);
IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId );
idToMarketItem[itemId].owner = payable(address(this));
idToMarketItem[itemId].sold = false;
payable(owner).transfer(listingPrice);
}
I am assuming that buy() transfers ownership to 'Address B' in the buy(). If not, How to grant approval for address 'B' to relist. If my line of thought is wrong, let me know what am I missing. Please guide me on how to relist the token/ freely tradable.