Как перенести NFT с одной учетной записи на другую с помощью ERC721?

Я пишу смарт-контракт и NFT, используя контракт OpenZeppelin ERC721Full. Я могу чеканить 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);

  }

Это не работает, потому что функция утверждения должна вызываться владельцем или уже утвержденным адресом. Я понятия не имею, как должна быть построена функция покупки. Я знаю, что должен использовать некоторые требования, но сначала я хочу, чтобы функция работала над тестами, а затем я напишу требования.

Как следует кодировать функцию покупки?Потому что единственное решение, которое я нашел, - это перезаписать функцию утверждения и опустить требование о том, кто может вызывать эту функцию. Но похоже, что это не так.

Спасибо!

3 ответа

Вы можете использовать только функцию _transfer() , см. Мой buy() функция для примера реализации.

Утверждения для продажи могут быть выполнены с использованием настраиваемого сопоставления - в моем примере tokenIdToPrice. Если значение не равно нулю, идентификатор токена (ключ сопоставления) продается.

Это базовый код, позволяющий продавать 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) получает идентификатор токена 1.
  2. Выполнять allowBuy(1, 2) что позволит любому купить токен ID 1 за 2 wei.
  3. Со второго адреса выполнить buy(1) отправка 2 wei, чтобы купить токен ID 1.
  4. Вызов (родительской ERC721) функции ownerOf(1) чтобы подтвердить, что владелец теперь является вторым адресом.

Если вы позволите кому-либо вызвать эту функцию, это позволит каждому одобрить использование NFT! Цель заключается в том, чтобы дать владельцу актива возможность дать кому-либо разрешение на передачу этого актива, как если бы он принадлежал им.

Основная предпосылка любой продажи заключается в том, что вы хотите быть уверены, что вам заплатят, и что покупатель получит товары в обмен на продажу. Решение Petr Hedja позаботится об этом, имея Функция не только передачи NFT, но и включает логику отправки цены токена. Хочу порекомендовать аналогичную структуру с небольшими изменениями. Один состоит в том, что функция также будет работать с токенами ERC20, другой - во избежание крайнего случая, когда, если газ закончится во время выполнения, покупатель может получить свой NFT бесплатно. Однако это основано на его ответе и свободно использует часть кода в этом ответе для архитектуры.

Эфир по-прежнему можно установить в качестве принятой валюты, введя нулевой адрес ( ) в качестве адреса контракта токена.

Если продажа осуществляется с помощью токена 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;
  }
Другие вопросы по тегам