DAPP开发【07】NFT交易市场开发(合约编写)

·开发一个 NFT 交易市场,功能如下
·用户在创建 NFT 时,可以指定价格
·NFT 信息上传到IPFS
·NFT 所有者可以下架市场里自己的 NFT
·用户可以在 NFT 市场以一定的价格购买 NFT
参考https://docs.openzeppelin.com/contracts/5.x/wizard
在这里插入图片描述
使用npm下载 npm install @openzeppelin/contracts可以引入openzeppelin库中的合约
erc20-usdt.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";//使用npm下载  npm install @openzeppelin/contracts
import "@openzeppelin/contracts/access/Ownable.sol";//下载后可以在node_modules中看到

contract cUSDT is ERC20{
    constructor() ERC20("ihan's erc20", "cUSDT")
    {
      _mint(msg.sender, 1*10**8*10**18); //1*10**8代表1亿,10**18精度是18
    }
}

erc721-nft.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract NFTM is ERC721, ERC721Enumerable, Ownable {
    uint256 private _nextTokenId;

    constructor(address initialOwner)
        ERC721("ihan's erc21", "NFTM")
        Ownable(initialOwner)
    {}

    function _baseURI() internal pure override returns (string memory) {
        return "http://ihan/";
    }

    function safeMint(address to) public onlyOwner {
        uint256 tokenId = _nextTokenId++;
        _safeMint(to, tokenId);
    }

    // The following functions are overrides required by Solidity.

    function _update(address to, uint256 tokenId, address auth)
        internal
        override(ERC721, ERC721Enumerable)
        returns (address)
    {
        return super._update(to, tokenId, auth);
    }

    function _increaseBalance(address account, uint128 value)
        internal
        override(ERC721, ERC721Enumerable)
    {
        super._increaseBalance(account, value);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721Enumerable)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}

nft-market.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC721.sol";
contract Market {
    IERC20 public erc20;
    IERC721 public erc721;

    bytes4 internal constant MAGIC_ON_ERC721_RECEIVED = 0x150b7a02;
    struct Order {
        address seller;
        uint256 tokenId;
        uint256 price;
    }
    mapping(uint256 => Order) public orderOfId;//token id to order
    
    Order[] public orders;
    mapping(uint256 => uint256) public idToOrderIndex;//token id to index in orders

    event Deal(address seller,address buyer,uint256 tokenId,uint256 price);
    event NewOrder(address seller,uint256 tokenId,uint256 price);
    event PriceChanged(address seller,uint256 tokenId,uint256 privousPrice,uint price);
    event OrderCancelled(address seller,uint256 tokenId);


    constructor(address _erc20,address _erc721){
        require(_erc20 != address(0),"zero address");
        require(_erc721 != address(0),"zero address");
        erc20 = IERC20(_erc20);
        erc721 = IERC721(_erc721);
    }

    function buy(uint256 _tokenId) external {
        address seller = orderOfId[_tokenId].seller;
        address buyer = msg.sender;
        uint256 price = orderOfId[_tokenId].price;
        //transferFrom代表market将钱由buyer转给seller,所以market需要被approve,market才能花a的钱,防止market私自花a的币
        require(erc20.transferFrom(buyer,seller,price),"transfer not successful");
        erc721.safeTransferFrom(address(this),buyer,_tokenId);//这里会调用hook函数吗?,不会
        //当您调用erc721.safeTransferFrom(address(this), seller, _tokenId)时,由于代币的接收方是seller地址,而不是当前合约地址address(this),因此不会触发onERC721Received函数。
        //safeTransferFrom函数遵循ERC-721标准,并具有以下行为:

    //如果接收方是一个智能合约,并且实现了onERC721Received函数,那么将调用该函数。
    //如果接收方是一个智能合约,但未实现onERC721Received函数,或者返回值不正确,那么将抛出异常并回滚交易。
    //如果接收方是一个普通的外部地址(例如用户地址),那么不会调用onERC721Received函数。
    //所以,在您的情况下,由于seller是一个外部地址,而不是一个智能合约地址,所以onERC721Received函数不会执行。

        removeOrder(_tokenId);
        emit Deal(seller,buyer,_tokenId,price);   
    }

    function cancelOrder(uint256 _tokenId) external {
        address seller = orderOfId[_tokenId].seller;
        require(msg.sender == seller,"not seller"); 
        erc721.safeTransferFrom(address(this),seller,_tokenId);
        removeOrder(_tokenId);
        emit OrderCancelled(seller, _tokenId); 
    }

    function changePrice(uint256 _tokenId,uint256 _price) external {
        address seller = orderOfId[_tokenId].seller;
        require(msg.sender == seller,"not seller");
 
        uint previousPrice = orderOfId[_tokenId].price;
        orderOfId[_tokenId].price = _price;

        //因为我们还定义了一个查看所有订单的数据结构,所以这个数据结构里需要改价 storage存储在区块链上,memory存储在内存中,没有修改区块链上的
        Order storage order = orders[idToOrderIndex[_tokenId]];
        order.price = _price;

        emit PriceChanged(seller, _tokenId, previousPrice,_price);
    }
    
    //规定,接受NFT的合约,必须实现onERC721Received方法,并且返回值是return this.onERC721Received.selector;没有正确的返回值则认为是不安全的
    //调用NFT合约的safeTransferFrom(四个参数)会自动调用这个方法,简称hook(钩子)方法
    //于是我们在NFT合约里调用safeTransferFrom,自动调用这个方法实现自动上架
    function onERC721Received (
        address operator,
        address from,
        uint tokenId,
        bytes calldata data
    ) external returns(bytes4){
        uint256 price = toUint256(data,0);//使用价格转换
        require(price > 0,"price must be greater than 0");

        //todo 上架
        
        orderOfId[tokenId] = Order(from,tokenId,price);
        orders.push(Order(from,tokenId,price));
        idToOrderIndex[tokenId] = orders.length - 1;

        emit NewOrder(from,tokenId,price); 

        //return this.onERC721Received.selector;
        return MAGIC_ON_ERC721_RECEIVED;
    }

    //上面函数我们接收到一个bytes 类型的data 里面包含价格信息,需要使用下面的函数进行转换

    function toUint256(
        bytes memory _bytes,
        uint256 _start
    )
    public pure returns (uint256){
        require(_start + 32 >= _start,"Market:toUint256_overflow");
        require(_bytes.length >= _start +32,"Market: toUint256_outOfBounds");
        uint256 tempUint;

        assembly{
            tempUint := mload(add(add(_bytes,0x20),_start))
        }
        return tempUint;

    }

    function removeOrder(uint256 _tokenId) internal{
        uint256 index = idToOrderIndex[_tokenId];
        uint256 lastIndex = orders.length-1;
        if(index != lastIndex){
            Order storage lastOrder = orders[lastIndex];
            orders[index] = lastOrder;
            idToOrderIndex[lastOrder.tokenId] = index;

        }
        orders.pop();
        delete idToOrderIndex[_tokenId];
        delete orderOfId[_tokenId];
    }

    //方便一个一个遍历
    function getOrderLength() external view returns(uint256){
        return orders.length;
    }
    
    //可以整个输出
    function getAllNFTs() external view returns(Order[] memory){
        return orders;
    }

    function getMyNFTs() external view returns(Order[] memory){
        Order[] memory myOrders = new Order[](orders.length);
        uint256 count = 0;
        for(uint256 i=0;i<orders.length;i++){
            if(orders[i].seller == msg.sender){
                myOrders[count]=orders[i];
                count++;
            }
        }
        return myOrders;

    }

    function isListed(uint256 _tokenId) public view returns(bool){
        return orderOfId[_tokenId].seller != address(0);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值