What's the correct way to implement NFT purchasing using ERC721?

I’m working on an NFT project using OpenZeppelin’s ERC721Full contract. Minting works fine, but I’m stuck on creating a purchase feature. Here’s what I’ve tried:

function purchaseToken(uint256 tokenId) public payable {
    address payable currentOwner = address(uint160(ownerOf(tokenId)));
    
    _approve(currentOwner, tokenId);
    setApprovalForAll(msg.sender, true);
    
    safeTransferFrom(currentOwner, msg.sender, tokenId);
    
    currentOwner.transfer(msg.value);
    
    emit TokenSold(currentOwner, msg.sender, msg.value);
}

This doesn’t work because only the owner or an approved address can call approve. I’m not sure how to structure this correctly. Should I change the approve function? That feels wrong.

What’s the right way to set up a buy function for NFTs? I’ll add checks later, but for now I just want it to work in testing. Thanks for any help!

hey there sparklinggem! ur nft purchasing setup is pretty close, but I think we can make it smoother :blush: have u considered using a marketplace contract? it could handle the buying and selling process for ya

instead of doing everything in one function, maybe try something like this:

contract NFTMarketplace {
    // store NFT prices
    mapping(uint256 => uint256) public tokenPrices;

    function listNFT(uint256 tokenId, uint256 price) external {
        // list NFT logic here
    }

    function buyNFT(uint256 tokenId) external payable {
        // buying logic here
    }
}

this way, the marketplace contract acts as a middleman. it keeps things organized and safer for everyone involved. what do u think about this approach? have u tried anything similar before?

also, just curious - what kind of NFTs are u working on? sounds like an exciting project! :art::sparkles:

hey, ur close but not quite there. instead of trying to do everything in one function, split it up. have the owner list the NFT for sale first, then make a separate buy function. That way, the contract can handle the transfer nd payment smoothly. here’s a quick example:

function listForSale(uint256 tokenId, uint256 price) public {
    // owner lists + approves
}

function buyToken(uint256 tokenId) public payable {
    // buyer pays, contract transfers
}

hope that helps!

Your approach is close, but there are a few key issues to address. First, the purchase function should be separate from the transfer logic. Instead, create a ‘listForSale’ function where the owner sets a price and approves the contract to transfer. Then, implement a ‘buyToken’ function that handles the payment and transfer.

Here’s a simplified example:

mapping(uint256 => uint256) public tokenPrices;

function listForSale(uint256 tokenId, uint256 price) public {
    require(ownerOf(tokenId) == msg.sender, 'Not the owner');
    tokenPrices[tokenId] = price;
    approve(address(this), tokenId);
}

function buyToken(uint256 tokenId) public payable {
    require(tokenPrices[tokenId] > 0, 'Not for sale');
    require(msg.value >= tokenPrices[tokenId], 'Insufficient payment');
    
    address payable seller = payable(ownerOf(tokenId));
    safeTransferFrom(seller, msg.sender, tokenId);
    seller.transfer(msg.value);
    
    tokenPrices[tokenId] = 0;
}

This structure separates concerns and maintains proper access control. Remember to add necessary checks and event emissions in a production environment.