当前位置:   article > 正文

零基础学会编写仿NFT交易市场的Dapp项目(二)

零基础学会编写仿NFT交易市场的Dapp项目(二)

文章整体目录

一、 Dapp、Web3的初步认识及项目整体架构

二、 NFT交易市场合约开发

三、 NFT交易市场后端开发

四、 NFT交易市场前端开发



一 、ERC是什么?

ERC 全称是“Ethereum Request for Comment”,表示以太坊的意见征求稿,ERC 中包含技术和组织等注意事项及标准。这套标准其实不光由以太坊官方提出,还由一些以太坊爱好者提出。是以太坊生态系统中被广泛使用的关键标准。

代币(token)标准

  • ERC-20 - 同质化(可互换)代币的标准接口,比如投票代币、质押代币或虚拟货币。
  • ERC-721 - 非同质化代币的标准接口,比如艺术作品或歌曲的契约。
  • ERC-777 - 关于 ERC-20 的代币标准改进。
  • ERC-1155 - 一个能包括同质化和非同质化资产的代币标准。

openzeppelin 智能合约库

官网:https://www.openzeppelin.com/

GitHub:https://github.com/OpenZeppelin/openzeppelin-contracts

OpenZeppelin 是一个使用以太坊智能合约语言 Solidity 进行构建的开发框架,可以简化智能合约和 Dapp 的开发。

OpenZeppelin合约和库已成为行业标准,其开源代码模板经历了以太坊及其他区块链的实战考验,帮助开发者最大限度降低风险。OpenZeppelin代码包括使用度最高的ERC标准及拓展部署,已被社区在各类指南以及操作教程中大量使用。

Contract Wizard

OpenZeppelin开发了一种基于网络的线上智能合约交互式工具,它可能是使用OpenZeppelin代码编写智能合约最简单快捷的方式。这一工具称为Contracts Wizard


二、hardhat工程的安装

  • 选择一个空文件
  • 进入命令行
npm install --save-dev hardhat
  • 1
  • 启动项目
npx hardhat
  • 1
  • 启动节点
npx hardhat node
  • 1

其他的命令请自行阅读README.md文件


三、ERC-20合约

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Xing is ERC20 {
    constructor() ERC20("fake usdt in cbi", "cUSDT") {
        _mint(msg.sender, 1 * 10**8 * 10**18); //发行一个亿的币
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

四、ERC-721合约

// 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("NFTM", "NFTM")
        Ownable(initialOwner)
    {}

    function _baseURI() internal pure override returns (string memory) {
        return "https://sameple.com/";
    }

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

以上代码均由https://docs.openzeppelin.com/contracts/5.x/wizard网址生成


五、NFTMarket交易市场的开发

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

// import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/interfaces/IERC20.sol";
// import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/interfaces/IERC721.sol";

import "@openzeppelin/contracts/interfaces/IERC721.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";

contract Market {
    IERC20 public erc20; //不写成address是为了以后可以直接调用合约的方法,不用显示转换
    IERC721 public erc721; //不写成address是为了以后可以直接调用合约的方法,不用显示转换

    //用户上架NTF过程中,hook钩子验证的一个验证码(类似于)
    bytes4 internal constant MAGIC_ON_ERC721_RECEIVED = 0x150b7a02;

    struct Order {
        address seller;
        uint256 tokenId;
        uint256 price;
    }
    /*将每个tokenId(非同质代币的唯一标识符)映射到一个 Order 结构。
      举例说明:如果某个用户想要出售自己的非同质代币,他会创建一个 Order 结构,
      然后将其与相应的 tokenId 关联。
      其他用户可以查询 orderOfId 来查看与特定 tokenId 相关联的订单。 */
    mapping(uint256 => Order) public orderOfId; //token id to order
    /**
      如果有多个用户分别出售不同的非同质代币,
      这些订单将存储在 orders 数组中,
      以便买家可以查看所有可用的订单。 */
    Order[] public orders;
    /**
    将每个 tokenId 映射到相应的订单在 orders 数组中的索引位置。
    这是为了方便后续对数组中订单的快速访问
    
    当一个新订单被创建并添加到 orders 数组时,
    idToOrderIndex 将被更新,使得可以通过 tokenId 快速找到订单在数组中的位置 */
    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 previousPrice,
        uint256 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;
        require(
            erc20.transferFrom(buyer, seller, price),
            "transfer not successful"
        ); //存在返回值的隐藏坑
        erc721.safeTransferFrom(address(this), buyer, _tokenId);
        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");
        uint256 previousPrice = orderOfId[_tokenId].price;
        //修改orderOfId里的价格
        orderOfId[_tokenId].price = _price;
        //修改order里的价格
        Order storage order = orders[idToOrderIndex[_tokenId]];
        order.price = _price;
        emit PriceChanged(seller, _tokenId, previousPrice, _price);
    }

    /**
        要删除的元素与最后一个元素交换
     */
    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 orderOfId[_tokenId];
        delete idToOrderIndex[_tokenId];
    }
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4) {
        uint256 price = toUint256(data, 0);
        require(price > 0, "price must be greater than 0");
        //上架
        orders.push(Order(from, tokenId, price));
        orderOfId[tokenId] = Order(from, tokenId, price);
        idToOrderIndex[tokenId] = orders.length - 1;
        emit NewOrder(from, tokenId, price);
        return MAGIC_ON_ERC721_RECEIVED;
    }

    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_out0fBounds");
        uint256 tempUint;
        assembly {
            tempUint := mload(add(add(_bytes, 0x20), _start))
        }
        return tempUint;
    }

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

    function getOrderLength() external view returns (uint256) {
        return orders.length;
    }

    function getAllNFTs() external view returns (Order[] memory) {
        return orders;
    }

    /**
        如果没被上架,那么格式就如下,地址为0 
        tuple(address,uint256,uint256)[]: 0x0000000000000000000000000000000000000000,0,0
     */
    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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162

重点!!

hook钩子,调用safeTransFrom会验证to地址是不是合约地址,若为合约地址,就会尝试调用onERC721Received方法,并且要求返回值为之前约定好的值(Bytes4)。
用户在调用NTF market(合约地址)的safeTransFrom,就会有个钩子自动调用onERC721Received。我们就需要再onERC721Received完成帮助用户上架NTF的功能。
用户和market两头是有个对接的,我们将价格以bytes4编码到safeTransFrom的data属性里去,market会自动解析为unit256的形式并上架
有了这个方法,NTF才能安全的被传送到_to地址


Finally

第一次写博客,有什么问题欢迎大家指出
有涉及到侵权请及时联系我!
接下来请浏览继续学习Dapp项目的开发(三)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/208445?site
推荐阅读
相关标签
  

闽ICP备14008679号