如何使用 ERC721 将 NFT 从一个账户转移到另一个账户?

How to transfer a NFT from one account to another using ERC721?

我正在使用 OpenZeppelin ERC721Full 合约编写 NFT 智能合约。我可以铸造 NFT,但我想要一个按钮来购买它们。我正在尝试编写此函数:

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);

  }

这不起作用,因为功能批准必须由所有者或已批准的地址调用。我不知道应该如何构建购买功能。我知道我必须使用一些要求,但首先我希望该功能能够在测试中运行,然后我将编写要求。

购买功能应该如何编码?因为我找到的唯一解决方案是覆盖批准功能并省略谁可以调用此功能的要求。但看起来这不是应该的方式。

谢谢!

您可以只使用 _transfer() 函数,有关实现示例,请参阅我的 buy() 函数。

可以使用自定义映射完成销售批准 - 在我的示例中 tokenIdToPrice。如果该值为非零,则代币 ID(映射密钥)是待售的。

这是允许销售 NTF 的基本代码。请随意扩展我的代码以允许“免费赠送”、“白名单买家”或任何其他功能。

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);
    }
}

如何模拟销售:

  1. 合约部署者 (msg.sender) 获得令牌 ID 1。
  2. 执行 allowBuy(1, 2) 将允许任何人以 2 wei 的价格购买代币 ID 1。
  3. 从第二个地址,执行buy(1)发送2 wei,购买代币ID 1。
  4. 调用(父 ERC721)函数 ownerOf(1) 以验证所有者现在是第二个地址。

如果让任何人调用approve函数,就可以让任何人批准自己拿NFT! approve 的目的是让资产的所有者能够允许其他人转让该资产,就好像它是他们的一样。

任何销售的基本前提是您要确保收到付款,并且买家在 return 收到商品以进行销售。 Petr Hedja 的解决方案通过让 buy 函数不仅传输 NFT,还包括发送代币价格的逻辑来解决这个问题。我想推荐一个类似的结构,但有一些变化。一个是该功能也可以与 ERC20 代币一起使用,另一个是防止出现边缘情况,即如果在执行过程中 gas 耗尽,买家最终可能会免费获得他们的 NFT。不过,这是建立在他的答案之上的,并且可以自由使用该答案中的一些代码来构建架构。

通过输入零地址(address(0))作为令牌的合约地址,仍然可以将以太币设置为可接受的货币。

如果销售采用 ERC20 代币,买方将需要批准 NFT 合约以支付销售金额,因为合约将直接从买方账户中提取资金。

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);
  }

这是 Nft 结构

struct NftItem{
    uint tokenId;
    uint price;
    // creator and owner are not same. creator someone who minted. creator does not change
    address creator;
    bool isListed;
  }