I have a very strange behavior when I try to sell NFT, when the token gets to the buyer, it turns out with nulled parameters. I can't understand why this is happening. Please explain what I'm doing wrong.
My function in contract:
function buyNft(uint tokenId) public payable {
uint price = _idToNftItem[tokenId].price;
address owner = ERC721.ownerOf(tokenId);
require(msg.sender != owner, 'You already own this NFT');
require(msg.value == price, "Please submit the asking price");
_idToNftItem[tokenId].isListed = false;
_listedItems.decrement();
_transfer(owner, msg.sender, tokenId);
payable(owner).transfer(msg.value);
}
My test code
Test result
Related
I am building a NFT Marketplace
I use 2 smart contracts to :
first, mint the token
then, call setApprovalForAll() to authorize the marketplace contract to transfer the token
strangely, the owner is address(0) when it should be the msg.sender when I created the token last
" The transaction has been reverted to the initial state.
Reason provided by the contract: "ERC721: invalid token ID". "
This the source code of the 2 smart contracts below :
smart contract : NFT
smart contract : NFTMarketplace
Thanks for the help
//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 contractAddress;
constructor(address marketplaceAddress) ERC721("NFTMarketplace", "NFTM") {
contractAddress = marketplaceAddress;
}
event TokenMinted (
uint256 indexed tokenId,
string tokenURI
);
function createToken(string memory tokenURI) public returns (uint) {
uint256 currentTokenId = _tokenIds.current();
_safeMint(msg.sender, currentTokenId);
_setTokenURI(currentTokenId, tokenURI);
setApprovalForAll(contractAddress, true);
_tokenIds.increment();
emit TokenMinted(currentTokenId, tokenURI);
return currentTokenId;
}
function getCurrentToken() public view returns (uint256) {
return _tokenIds.current();
}
}
contract NFTMarketplace is ReentrancyGuard, ERC721 {
using Counters for Counters.Counter;
//_tokenIds variable has the most recent minted tokenId
//Keeps track of the number of items sold on the marketplace
Counters.Counter private _itemsSold;
//owner is the contract address that created the smart contract
address payable owner;
//The fee charged by the marketplace to be allowed to list an NFT
uint256 listPrice = 0.01 ether;
constructor() ERC721("NFTMarketplace", "NFTM") {}
//The structure to store info about a listed token
struct Token {
uint256 tokenId;
string tokenURI;
address nftContract;
string name;
address payable owner;
uint256 price;
bool isListed;
}
//the event emitted when a token is successfully listed
event TokenListedSuccess (
uint256 indexed tokenId,
address nftContract,
address owner,
uint256 price,
bool isListed
);
//This mapping maps tokenId to token info and is helpful when retrieving details about a tokenId
mapping(uint256 => Token) private idToToken;
function updateListPrice(uint256 _listPrice) public payable {
require(owner == msg.sender, "Only owner can update listing price");
listPrice = _listPrice;
}
//The first time a token is created, it is listed here
// make it payable with money -- add require
function listToken(address nftContract, uint256 currentTokenId, string memory tokenURI, string memory name, uint256 price) public payable {
require(msg.value > 0, "Price must be at least 1 wei");
require(msg.value == listPrice, "Price must be equal to listing price");
idToToken[currentTokenId] = Token(
currentTokenId,
tokenURI,
nftContract,
name,
payable(address(this)),
listPrice,
true
);
emit TokenListedSuccess(
currentTokenId,
nftContract,
msg.sender,
listPrice,
true
);
}
function buyNFT(address nftContract, uint256 itemId) public payable nonReentrant {
uint price = idToToken[itemId].price;
uint tokenId = idToToken[itemId].tokenId;
address seller = ERC721.ownerOf(tokenId);
address buyer = msg.sender;
require(msg.value > 0, "You need to send some ether");
require(buyer != seller,"You already own this nft");
require(msg.value == price, "Please submit the asking price in order to complete the purchase");
idToToken[itemId].isListed = false;
idToToken[itemId].owner = payable(buyer);
payable(seller).transfer(price);
_itemsSold.increment();
IERC721(nftContract).transferFrom(seller, buyer, tokenId);
}
/* ...rest of smart contract */
}
I think you forget to assign "owner" value.
Typically, we set it in constructor.
like
constructor() {
i_owner = msg.sender;
}
Can someone explain to me how I can set approval for ERC721 contract ?
1- At first, the token once minted belongs to the marketplace
2- with buy, I need to transfer ownership of the token to the caller
I keep getting these errors
X Fail with error 'ERC721: approve caller is not token owner nor approved for all'
x Fail with error 'ERC721: approve to caller'
This is my smart contract : the mint buy function
//The first time a token is created, it is listed here
function createToken(string memory tokenURI, string memory name, uint256 price) public payable returns (uint) {
uint256 currentTokenId = _tokenIds.current();
//Mint the NFT with tokenId newTokenId to the address who called createToken
_safeMint(msg.sender, currentTokenId);
//Map the tokenId to the tokenURI (which is an IPFS URL with the NFT metadata)
_setTokenURI(currentTokenId, tokenURI);
//Helper function to update Global variables and emit an event
_create(currentTokenId, tokenURI, name, listPrice);
setApprovalForAll(address(this), true);
//Increment the tokenId counter, which is keeping track of the number of minted NFTs
_tokenIds.increment();
uint256 newTokenId = _tokenIds.current();
return newTokenId;
}
function _create(uint256 tokenId, string memory tokenURI, string memory name, uint256 price) private {
//Make sure the sender sent enough ETH to pay for listing
require(msg.value == listPrice, "Hopefully sending the correct price");
//Just sanity check
require(price > 0, "Make sure the price isn't negative");
//Update the mapping of tokenId's to Token details, useful for retrieval functions
idToToken[tokenId] = Token(
tokenId,
tokenURI,
name,
payable(address(this)),
price,
true
);
_transfer(msg.sender, address(this), tokenId);
//Emit the event for successful transfer. The frontend parses this message and updates the end user
emit TokenListedSuccess(
tokenId,
address(this),
price,
true
);
}
function buyNFT(uint256 tokenId) public payable {
require(msg.value > 0, "You need to send some ether");
require(msg.value == idToToken[tokenId].price, "Please submit the asking price in order to complete the purchase");
approve(msg.sender, tokenId);
setApprovalForAll(msg.sender, true);
transferFrom(address(this), msg.sender,tokenId);
// safeTransferFrom(address(this), msg.sender, tokenId);
payable(idToToken[tokenId].owner).transfer(msg.value);
//update the details of the token
idToToken[tokenId].owner = payable(msg.sender);
idToToken[tokenId].isForSale = false;
_itemsSold.increment();
//Transfer the proceeds from the sale to the seller of the NFT
payable(address(this)).transfer(msg.value);
// emit Event
}
this is my client app :
const buyToken = (...args) => {
const [tokenId] = args
return new Promise(async resolve => {
try {
let transaction = await contract.buyNFT(tokenId, {
gasLimit: 5500000,
value: price.toString()
});
await transaction.wait();
resolve()
} catch (e) {
console.error(e)
}
})
}
I think the issue is with
transferFrom(address(this), msg.sender,tokenId);
You are transferring from the contract address to the msg.sender but it should be from the nft owner or token id owner.
The approve() function grants a third party the ability to transfer a
single token id. setApprovalForAll() will grant a third party the
ability to transfer all of the tokens you own for a given collection.
When we mint the token we call setApprovalForAll() to authorize the marketplace to transfer our token. This is the trasferFrom
function transferFrom(
address from,
address to,
uint256 tokenId
) public virtual override {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
_transfer(from, to, tokenId);
}
Before transferring it is checking if the msg.sender is already authorized for this transaction or from address is the "owner" of token. To solve the issue get the owner of the tokenId. Since you are passing the "tokenIdto thebuyNFT`
address owner=ERC721.ownerOf(tokenId);
transferFrom(owner, msg.sender,tokenId);
What is the difference between msg.sender and address(this) in the below code?
**pragma solidity ^0.8.0;
contract Escrow{
address public payer;
address payable public payee;
address public lawyer;
uint public amount;
constructor(
address _payer,
address payable _payee,
uint _amount) {
payer = _payer;
payee = _payee;
lawyer = msg.sender;
amount = _amount;
}
function deposit() payable public {
require(msg.sender == payer, 'Sender must be the payer');
require(address(this).balance <= amount, 'Cant send more than escrow amount');
}
function release() public {
require(address(this).balance == amount, 'cannot release funds before full amount is sent');
require(msg.sender == lawyer, 'only lawyer can release funds');
payee.transfer(amount);
}
function balanceOf() view public returns(uint) {
return address(this).balance;
}
}**
msg.sender is the address of the contract caller.
address(this) is the address of the smart contract itself.
They are both addresses in Solidity, but there is a big difference between msg.sender and address(this).
Allow me to use a simplified Smart Contract below to highlight the difference. All screenshots are from the Remix-Ethereum IDE (click here).
pragma solidity ^0.8.0;
contract Escrow {
address public owner;
constructor() {
owner = msg.sender;
}
function depositNothing() public view {
require(msg.sender == owner, 'You are not the owner!');
}
function balanceOf() view public returns(uint) {
return address(this).balance;
}
}
msg.sender
We are talking about the ACCOUNT address from which the function in the Smart Contract was called. For example, suppose in the Remix Ethereum (IDE), the Escrow Smart Contract was deployed from the ACCOUNT address:
0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
In that case, the State Variable owner will have the same address mentioned above. This is because the constructor function was called from that address.
Now, suppose we change the ACCOUNT address to:
0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
We then call the function depositNothing from the Smart Contract which was deployed earlier. You will get the following error, however:
This is because the msg.sender in the depositNothing function equates to the second ACCOUNT address. This obviously does not equate to the first ACCOUNT Address - owner. The second argument in the require function was therefore returned along with the error.
address(this)
This is not the same as the ACCOUNT Address discussed earlier. This strictly refers to the address given to the Smart Contract when it is deployed to the Ethereum blockchain.
This can be found here:
0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8
I have the following smart contract:
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./MyCoinSupply.sol";
contract MyCoinDEX
{
IERC20 public token;
event Bought(uint256 amount);
event Sold(uint256 amount);
constructor() public
{
token = new MyCoinSupply();
}
function getSenderAddress() public view returns (address) // for debugging purposes
{
return (msg.sender);
}
function getAddress() public view returns (address)
{
return address(this);
}
function getTokenAddress() public view returns (address)
{
return address(token);
}
function buy() payable public // send ether and get tokens in exchange; 1 token == 1 ether
{
uint256 amountTobuy = msg.value;
uint256 dexBalance = token.balanceOf(address(this));
require(amountTobuy > 0, "You need to send some ether");
require(amountTobuy <= dexBalance, "Not enough tokens in the reserve");
token.transfer(msg.sender, amountTobuy);
emit Bought(amountTobuy);
}
function sell(uint256 amount) public // send tokens to get ether back
{
require(amount > 0, "You need to sell at least some tokens");
uint256 allowance = token.allowance(msg.sender, address(this));
require(allowance >= amount, "Check the token allowance");
token.transferFrom(msg.sender, address(this), amount);
// https://stackoverflow.com/questions/67341914/error-send-and-transfer-are-only-available-for-objects-of-type-address-payable
payable(msg.sender).transfer(amount);
emit Sold(amount);
}
}
if I call the buy() method from truffle console, it executes without any exceptions:
truffle(development)> MyCoinDEX.buy({value: 1})
I verified that the account calling the buy() method receives the token. However, the balance of Ether in Ganache for the account calling the buy() method doesn't decrease. So essentially, the account is getting tokens for free.
What's going on here? How do I fix it?
I am not sure but it would be necessary to investigate about the balance of the contract account. The gas for token.transfer might be paid by the contract account balance, since the contract account is the transaction sender for token contract.
Or the balance decrement would be unnoticeable because it is too small.
I would be happy to know about the answer if you have found out.
I'm writing an NFT smart contract using the OpenZeppelin ERC721Full contract. I'm able to mint NFTs, but I want to have a button that enables them to be bought. I'm trying writing this function:
function buyNFT(uint _id) public payable{
//Get NFT owner address
address payable _seller = ownerOf(_id);
// aprove nft sell
approve(_seller, _id);
setApprovalForAll(msg.sender, true);
//transfer NFT
transferFrom(_seller, msg.sender, _id);
// transfer price in ETH
address(_seller).transfer(msg.value);
emit NftBought(_seller, msg.sender, msg.value);
}
This does not work because function approve must be called by the owner or an already approved address. I have no clue on how a buy function should be built. I know that I must use some requirements but first I want the function to work on tests and then I'll write the requirements.
How should a buy function be coded? Because the only solution I have found is to overwrite the approve function and omit the require of who can call this function. But it looks like it isn't the way it should be done.
Thank you!
You can use just the _transfer() function, see my buy() function for an example of implementation.
The approvals for sale can be done using a custom mapping - in my example tokenIdToPrice. If the value is non-zero, the token ID (mapping key) is for sale.
This is a basic code that allows selling an NTF. Feel free to expand on my code to allow "give away for free", "whitelist buyers" or any other feature.
pragma solidity ^0.8.4;
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol';
contract MyToken is ERC721 {
event NftBought(address _seller, address _buyer, uint256 _price);
mapping (uint256 => uint256) public tokenIdToPrice;
constructor() ERC721('MyToken', 'MyT') {
_mint(msg.sender, 1);
}
function allowBuy(uint256 _tokenId, uint256 _price) external {
require(msg.sender == ownerOf(_tokenId), 'Not owner of this token');
require(_price > 0, 'Price zero');
tokenIdToPrice[_tokenId] = _price;
}
function disallowBuy(uint256 _tokenId) external {
require(msg.sender == ownerOf(_tokenId), 'Not owner of this token');
tokenIdToPrice[_tokenId] = 0;
}
function buy(uint256 _tokenId) external payable {
uint256 price = tokenIdToPrice[_tokenId];
require(price > 0, 'This token is not for sale');
require(msg.value == price, 'Incorrect value');
address seller = ownerOf(_tokenId);
_transfer(seller, msg.sender, _tokenId);
tokenIdToPrice[_tokenId] = 0; // not for sale anymore
payable(seller).transfer(msg.value); // send the ETH to the seller
emit NftBought(seller, msg.sender, msg.value);
}
}
How to simulate the sale:
The contract deployer (msg.sender) gets token ID 1.
Execute allowBuy(1, 2) that will allow anyone to buy token ID 1 for 2 wei.
From a second address, execute buy(1) sending along 2 wei, to buy the token ID 1.
Call (the parent ERC721) function ownerOf(1) to validate that the owner is now the second address.
If you let anyone call the approve function, it would allow anyone to approve themselves to take NFTs! The purpose of approve is to give the owner of an asset the ability to give someone else permission to transfer that asset as if it was theirs.
The basic premise of any sale is that you want to make sure that you get paid, and that the buyer receives the goods in return for the sale. Petr Hedja's solution takes care of this by having the buy function not only transfer the NFT, but also include the logic for sending the price of the token. I'd like to recommend a similar structure with a few changes. One is so that the function will also work with ERC20 tokens, the other is to prevent an edge case where if gas runs out during execution, the buyer could end up with their NFT for free. This is building on his answer, though, and freely uses some of the code in that answer for architecture.
Ether can still be set as the accepted currency by inputting the zero address (address(0)) as the contract address of the token.
If the sale is in an ERC20 token, the buyer will need to approve the NFT contract to spend the amount of the sale since the contract will be pulling the funds from the buyer's account directly.
pragma solidity ^0.8.4;
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol';
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol';
contract MyToken is ERC721 {
event NftBought(address _seller, address _buyer, uint256 _price);
mapping (uint256 => uint256) public tokenIdToPrice;
mapping (uint256 => address) public tokenIdToTokenAddress;
constructor() ERC721('MyToken', 'MyT') {
_mint(msg.sender, 1);
}
function setPrice(uint256 _tokenId, uint256 _price, address _tokenAddress) external {
require(msg.sender == ownerOf(_tokenId), 'Not owner of this token');
tokenIdToPrice[_tokenId] = _price;
tokenIdToTokenAddress[_tokenId] = _tokenAddress;
}
function allowBuy(uint256 _tokenId, uint256 _price) external {
require(msg.sender == ownerOf(_tokenId), 'Not owner of this token');
require(_price > 0, 'Price zero');
tokenIdToPrice[_tokenId] = _price;
}
function disallowBuy(uint256 _tokenId) external {
require(msg.sender == ownerOf(_tokenId), 'Not owner of this token');
tokenIdToPrice[_tokenId] = 0;
}
function buy(uint256 _tokenId) external payable {
uint256 price = tokenIdToPrice[_tokenId];
require(price > 0, 'This token is not for sale');
require(msg.value == price, 'Incorrect value');
address seller = ownerOf(_tokenId);
address tokenAddress = tokenIdToTokenAddress[_tokenId];
if(address != address(0){
IERC20 tokenContract = IERC20(tokenAddress);
require(tokenContract.transferFrom(msg.sender, address(this), price),
"buy: payment failed");
} else {
payable(seller).transfer(msg.value);
}
_transfer(seller, msg.sender, _tokenId);
tokenIdToPrice[_tokenId] = 0;
emit NftBought(seller, msg.sender, msg.value);
}
}
// mapping is for fast lookup. the longer operation, the more gas
mapping(uint => NftItem) private _idToNftItem;
function buyNft(uint tokenId) public payable{
uint price=_idToNftItem[tokenId].price;
// this is set in erc721 contract
// Since contracts are inheriting, I want to make sure I use this method in ERC721
address owner=ERC721.ownerOf(tokenId);
require(msg.sender!=owner,"You already own this nft");
require(msg.value==price,"Please submit the asking price");
// since this is purchased, it is not for sale anymore
_idToNftItem[tokenId].isListed=false;
_listedItems.decrement();
// this is defined in ERC721
// this already sets owner _owners[tokenId] = msg.sender;
_transfer(owner,msg.sender,tokenId);
payable(owner).transfer(msg.value);
}
this is Nft struct
struct NftItem{
uint tokenId;
uint price;
// creator and owner are not same. creator someone who minted. creator does not change
address creator;
bool isListed;
}