简体   繁体   English

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

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

I'm writing an NFT smart contract using the OpenZeppelin ERC721Full contract.我正在使用 OpenZeppelin ERC721Full 合约编写 NFT 智能合约。 I'm able to mint NFTs, but I want to have a button that enables them to be bought.我可以铸造 NFT,但我想要一个按钮来购买它们。 I'm trying writing this function:我正在尝试写这个 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.这不起作用,因为 function 批准必须由所有者或已批准的地址调用。 I have no clue on how a buy function should be built.我不知道应该如何构建 buy function。 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.我知道我必须使用一些要求,但首先我希望 function 能够进行测试,然后我将编写要求。

How should a buy function be coded?购买function应该如何编码? 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.因为我找到的唯一解决方案是覆盖 approve function 并省略 who can call this function 的要求。但看起来这不是应该完成的方式。

Thank you!谢谢!

You can use just the _transfer() function, see my buy() function for an example of implementation.您可以只使用_transfer() function,请参阅我的buy() function 以获取实现示例。

The approvals for sale can be done using a custom mapping - in my example tokenIdToPrice .可以使用自定义映射来完成销售批准 - 在我的示例中tokenIdToPrice If the value is non-zero, the token ID (mapping key) is for sale.如果该值非零,则令牌 ID(映射键)正在出售。

This is a basic code that allows selling an NTF.这是允许销售 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:如何模拟销售:

  1. The contract deployer ( msg.sender ) gets token ID 1.合约部署者 ( msg.sender ) 获得令牌 ID 1。
  2. Execute allowBuy(1, 2) that will allow anyone to buy token ID 1 for 2 wei.执行allowBuy(1, 2)将允许任何人以 2 wei 的价格购买代币 ID 1。
  3. From a second address, execute buy(1) sending along 2 wei, to buy the token ID 1.从第二个地址执行buy(1)发送 2 wei,购买 ID 为 1 的代币。
  4. Call (the parent ERC721) function ownerOf(1) to validate that the owner is now the second address.调用(父 ERC721)function ownerOf(1)以验证所有者现在是第二个地址。

If you let anyone call the approve function, it would allow anyone to approve themselves to take NFTs!如果您让任何人调用approve function,它将允许任何人批准自己接受 NFT! 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. approve的目的是让资产的所有者能够允许其他人转移该资产,就好像它是他们的一样。

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. Petr Hedja 的解决方案通过让buy function 不仅转移 NFT,还包括发送代币价格的逻辑来解决这个问题。 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.一个是为了让 function 也可以与 ERC20 代币一起使用,另一个是为了防止在执行过程中如果 gas 用完,买家最终可以免费获得 NFT。 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.通过输入零地址( address(0) )作为代币的合约地址,以太币仍然可以设置为接受的货币。

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.如果以 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);
  }

this is Nft struct这是 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;
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM