主頁 >  其他 > 如何使用Solidity和Hardhat構建你自己的NFT以及NFT交易市場

如何使用Solidity和Hardhat構建你自己的NFT以及NFT交易市場

2022-08-06 10:05:32 其他

目錄

目錄
  • 目錄
  • 1、ERC721的基礎知識
    • 1.1、什么是不可替代代幣?
    • 1.2、什么是 ERC-721?
    • 1.3、什么是元資料
    • 1.4、如何在鏈上保存NFT的影像
  • 2、HardHat
  • 3、創建專案
    • 3.1、創建 NFT 市場
    • 3.2、創建 NFT 智能合約
    • 3.3、撰寫測驗腳本
  • 4、將 NFT 部署到 Rinkeby 網路,在 OpenSea 上查看
    • 4.1、部署 NFT市場
    • 4.2、部署 NFT 721示例
    • 4.3、對 NFT 721示例 合約在 Rinkeby 網路進行驗證
    • 4.4、在 Rinkeby 網路鑄造 NFT
    • 4.5、在 opensea 查看剛剛鑄造的NFT
  • 5、專案原始碼
  • 6、推薦閱讀

1、ERC721的基礎知識

1.1、什么是不可替代代幣?

NFT 是獨一無二的,每個令牌都有獨特的特征和價值,可以成為 NFT 的東西型別有收藏卡、藝術品、機票等,它們之間都有明顯的區別,不可互換,將不可替代代幣 (NFT) 視為稀有收藏品;而且大多數時候,還有它的元資料屬性,

1.2、什么是 ERC-721?

ERC-721(Ethereum Request for Comments 721)由 William Entriken、Dieter Shirley、Jacob Evans 和 Nastassia Sachs 于 2018 年 1 月提出,是一種不可替代的代幣標準,描述了如何在 EVM(以太坊虛擬機)兼容的區塊鏈上構建不可替代的代幣;它是不可替代代幣的標準介面;它有一套規則,可以很容易地使用 NFTNFT 不僅是 ERC-721 型別;它們也可以是ERC-1155 令牌,

ERC-721 引入了 NFT 標準,換句話說,這種型別的 Token 是獨一無二的,并且可能具有與來自同一智能合約的另一個 Token 不同的價值,可能是由于它的年齡、稀有性甚至是其他類似自定義屬性等等,

所有 NFT 都有一個 uint256 型別的變數 tokenId,因此對于任何 ERC-721 合約,該對 contract addressuint256 tokenId 必須是全域唯一的,也就是說,一個 dApp 可以有一個“轉換器”,它使用 tokenId 作為輸入并輸出一些很酷的東西的影像,比如僵尸、武器、技能或貓、狗一類的!

1.3、什么是元資料

所有 NFT 都有元資料,您可以在最初的ERC/EIP 721 提案中了解這一點 , 基本上,社區發現在以太坊上存盤影像真的很費力而且很昂貴,如果你想存盤一張 8 x 8 的圖片,存盤這么多資料是相當便宜的,但如果你想要一張解析度不錯的圖片,你就需要花更多的 GAS 費用,

雖然 以太坊 2.0 將解決很多這些擴展難題,但目前,社區需要一個標準來幫助解決這個問題,這也就是元資料的存在原因,

{
    "name": "mshk-logo-black",
    "description": "mshk.top logo black",
    "image": "https://bafybeihodzhbtntgml7t72maxill576ssax6md5kfu72aq4gd4p53oipn4.ipfs.infura-ipfs.io/",
    "attributes": [
        {
            "trait_type": "customAttr",
            "value": 100
        }
    ]
}

name,NFT的代幣名稱
description,NFT的代幣描述
image,NFT影像的URL
attributes,NFT代幣的屬性,可以定義多個

一旦我們將 tokenId 分配給他們的 tokenURINFT 市場將能夠展示你的代幣,您可以在 Rinkeby 測驗網上的 OpenSea 市場上看到我使用元資料更新后的效果,類似展示 NFT 的市場 還有如 Mintable、Rarible,
mshk.top

1.4、如何在鏈上保存NFT的影像

您會在上面的元資料代碼示例中注意到,影像使用指向 IPFSURL,這是一種流行的影像存盤方式,

IPFS 代表星際檔案系統,是一種點對點超媒體協議,旨在使網路更快、更安全、更開放,它允許任何人上傳檔案,并且該檔案被散列,因此如果它發生變化,它的散列也會發生變化,這是存盤影像的理想選擇,因為這意味著每次更新影像時,鏈上的 hash/tokenURI 也必須更改,這意味著我們可以記錄元資料的歷史記錄,將影像添加到 IPFS 上也非常容易,并且不需要運行服務器!

推薦使用 CoinTool 中的 IPFS 工具,

2、HardHat

關于 HardHat 的介紹以及安裝,可以參考文章 如何使用ERC20代幣實作買、賣功能并完成Dapp部署

3、創建專案

3.1、創建 NFT 市場

進入 hardhat 專案目錄,創建 contracts/ERC721/NftMarketplace.sol 檔案,內容如下:

$ cat contracts/ERC721/NftMarketplace.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

// Check out https://github.com/Fantom-foundation/Artion-Contracts/blob/5c90d2bc0401af6fb5abf35b860b762b31dfee02/contracts/FantomMarketplace.sol
// For a full decentralized nft marketplace

// 從Solidity v0.8.4開始,有一種方便且省 gas 的方式可以通過使用自定義錯誤向用戶解釋操作失敗的原因,
// 錯誤的語法類似于 事件的語法,它們必須與revert 陳述句一起使用,這會導致當前呼叫中的所有更改都被還原并將錯誤資料傳遞回呼叫者
error PriceNotMet(address nftAddress, uint256 tokenId, uint256 price);
error ItemNotForSale(address nftAddress, uint256 tokenId);
error NotListed(address nftAddress, uint256 tokenId);
error AlreadyListed(address nftAddress, uint256 tokenId);
error NoProceeds();
error NotOwner();
error NotApprovedForMarketplace();
error PriceMustBeAboveZero();

contract NftMarketplace is ReentrancyGuard {
    // 保存賣家地址和價格
    struct Listing {
        uint256 price;
        address seller;
    }

    // 加入市場串列事件
    event ItemListed(
        address indexed seller,
        address indexed nftAddress,
        uint256 indexed tokenId,
        uint256 price
    );

    // 更新事件
    event UpdateListed(
        address indexed seller,
        address indexed nftAddress,
        uint256 indexed tokenId,
        uint256 price
    );

    // 取消市場串列事件
    event ItemCanceled(
        address indexed seller,
        address indexed nftAddress,
        uint256 indexed tokenId
    );

    // 買入事件
    event ItemBuy(
        address indexed buyer,
        address indexed nftAddress,
        uint256 indexed tokenId,
        uint256 price
    );

    // 保存NFT串列和賣家的對應狀態
    mapping(address => mapping(uint256 => Listing)) private s_listings;

    // 賣家地址和賣出的總金額
    mapping(address => uint256) private s_proceeds;

    modifier notListed(
        address nftAddress,
        uint256 tokenId,
        address owner
    ) {
        Listing memory listing = s_listings[nftAddress][tokenId];
        if (listing.price > 0) {
            revert AlreadyListed(nftAddress, tokenId);
        }
        _;
    }

    // 檢查賣家是否在串列中
    modifier isListed(address nftAddress, uint256 tokenId) {
        Listing memory listing = s_listings[nftAddress][tokenId];
        if (listing.price <= 0) {
            revert NotListed(nftAddress, tokenId);
        }
        _;
    }

    // 檢查 NFT 地址的 tokenId owner 是否為 spender
    modifier isOwner(
        address nftAddress,
        uint256 tokenId,
        address spender
    ) {
        IERC721 nft = IERC721(nftAddress);

        // 查找NFT的所有者,分配給零地址的 NFT 被認為是無效的,回傳NFT持有者地址
        address owner = nft.ownerOf(tokenId);
        if (spender != owner) {
            revert NotOwner();
        }
        _;
    }

    /*
     * @notice 將 NFT 加入到市場串列中,external 表示這是一個外部函式
     * @param nftAddress Address of NFT contract
     * @param tokenId Token ID of NFT
     * @param price sale price for each item
     */
    function listItem(
        address nftAddress,
        uint256 tokenId,
        uint256 price
    )
        external
        notListed(nftAddress, tokenId, msg.sender)
        isOwner(nftAddress, tokenId, msg.sender)
    {
        if (price <= 0) {
            // 終止運行并撤銷狀態更改
            revert PriceMustBeAboveZero();
        }
        IERC721 nft = IERC721(nftAddress);
        // 獲取單個NFT的批準地址,如果tokenId不是有效地址,拋出例外,
        if (nft.getApproved(tokenId) != address(this)) {
            revert NotApprovedForMarketplace();
        }

        // 存盤智能合約狀態
        s_listings[nftAddress][tokenId] = Listing(price, msg.sender);

        // 注冊事件
        emit ItemListed(msg.sender, nftAddress, tokenId, price);
    }

    /*
     * @notice 從NFT串列中洗掉 賣家資訊
     * @param nftAddress Address of NFT contract
     * @param tokenId Token ID of NFT
     */
    function cancelListing(address nftAddress, uint256 tokenId)
        external
        isOwner(nftAddress, tokenId, msg.sender)
        isListed(nftAddress, tokenId)
    {
        delete (s_listings[nftAddress][tokenId]);

        // 注冊 事件
        emit ItemCanceled(msg.sender, nftAddress, tokenId);
    }

    /*
     * @notice 允許買家使用ETH,從賣家串列中買入 NFT
     * nonReentrant 方法 防止合約被重復呼叫
     * @param nftAddress NFT 合約地址
     * @param tokenId NFT 的通證 ID
     */
    function buyItem(address nftAddress, uint256 tokenId)
        external
        payable
        isListed(nftAddress, tokenId)
        nonReentrant
    {
        // 獲取賣家串列,并判斷支付的ETH是否小于賣家的價格
        Listing memory listedItem = s_listings[nftAddress][tokenId];
        if (msg.value < listedItem.price) {
            revert PriceNotMet(nftAddress, tokenId, listedItem.price);
        }

        // 更新賣家賣出的金額
        s_proceeds[listedItem.seller] += msg.value;
        // Could just send the money...
        // https://fravoll.github.io/solidity-patterns/pull_over_push.html

        // 從賣家串列中洗掉
        delete (s_listings[nftAddress][tokenId]);

        // 將 NFT(tokenId) 所有權從 listedItem.seller 轉移到  msg.sender
        IERC721(nftAddress).safeTransferFrom(
            listedItem.seller,
            msg.sender,
            tokenId
        );

        //注冊買家事件
        emit ItemBuy(msg.sender, nftAddress, tokenId, listedItem.price);
    }

    /*
     * @notice 賣家更新NFT在市場上的價格
     * @param nftAddress Address of NFT contract
     * @param tokenId Token ID of NFT
     * @param newPrice Price in Wei of the item
     */
    function updateListing(
        address nftAddress,
        uint256 tokenId,
        uint256 newPrice
    )
        external
        isListed(nftAddress, tokenId)
        nonReentrant
        isOwner(nftAddress, tokenId, msg.sender)
    {
        s_listings[nftAddress][tokenId].price = newPrice;
        emit UpdateListed(msg.sender, nftAddress, tokenId, newPrice);
    }

    /*
     * @notice 將ETH轉移到其他帳號,同時設定收益余額為0
     */
    function withdrawProceeds() external {
        uint256 proceeds = s_proceeds[msg.sender];
        if (proceeds <= 0) {
            revert NoProceeds();
        }
        s_proceeds[msg.sender] = 0;

        // 將 ETH 發送到地址的方法,關于此語法更多介紹可以參考下面鏈接
        // https://ethereum.stackexchange.com/questions/96685/how-to-use-address-call-in-solidity
        (bool success, ) = payable(msg.sender).call{value: proceeds}("");
        require(success, "Transfer failed");
    }

    /*
     * @notice 獲取NFT賣家串列
     */
    function getListing(address nftAddress, uint256 tokenId)
        external
        view
        returns (Listing memory)
    {
        return s_listings[nftAddress][tokenId];
    }

    // 獲取 seller 賣出的總金額
    function getProceeds(address seller) external view returns (uint256) {
        return s_proceeds[seller];
    }
}

從 Solidity v0.8.4開始,有一種方便且省 GAS 的方式可以通過使用自定義錯誤向用戶解釋操作失敗的原因,錯誤的語法類似于事件的語法,它們必須與 revert 陳述句一起使用,這會導致當前呼叫中的所有更改都被還原并將錯誤資料傳遞回呼叫者,
??自定義錯誤是在智能合約主體之外宣告的,當錯誤被拋出時,在 Solidity 中意味著當某些檢查和條件失敗,周圍函式的執行被“還原”,

代碼中主要內容介紹:

  • notListed、isListed、isOwner是函式修飾符的應用,
  • listItem方法,將 NFT 加入到串列,會做一些權限驗證,其中用到了函式修飾符和事件
  • cancelListing方法,從串列中洗掉 NFT,將 NFT 下架,
  • buyItem方法,購買 NFT ,專案中主要用 ETH 來交換 NFT 資產,也可以用其他數字資產進行交換,同時會更新賣家余額,從listItem中下架 NFT
  • updateListing方法,更新 NFT 的價格,
  • withdrawProceeds方法,將賣出的收益從合約中轉移給賣家,
  • getListing方法,根據 NFT 地址和 tokenId,回傳賣家和價格資訊,
  • getProceeds方法,查看賣家賣出后的收益,

3.2、創建 NFT 智能合約

在撰寫測驗腳本前,我們需要一個 NFT的智能合約示例,以便我們鑄造的 NFT可以在市場上展示、銷售,我們將遵守 ERC721 令牌規范,我們將從 OpenZeppelinERC721URIStorage 庫繼承,

進入 hardhat 專案目錄,創建 contracts/ERC721/MSHK721NFT.sol 檔案,內容如下:

$ cat contracts/ERC721/MSHK721NFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;

import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "hardhat/console.sol";

contract MSHK721NFT is ERC721URIStorage, Ownable {
    // 遞增遞減計數器
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    // 宣告事件
    event NFTMinted(uint256 indexed tokenId);

    constructor() ERC721("MSHKNFT", "MyNFT") {}

    /**
     * 制作NFT,回傳鑄造的 NFT ID
     * @param recipient 接收新鑄造NFT的地址.
     * @param tokenURI 描述 NFT 元資料的 JSON 檔案
     */
    function mintNFT(address recipient, string memory tokenURI)
        external
        onlyOwner
        returns (uint256)
    {
        // 遞增
        _tokenIds.increment();

        // 獲取當前新的 TokenId
        uint256 newTokenId = _tokenIds.current();

        // 鑄造NFT
        _safeMint(recipient, newTokenId);

        // 保存NFT URL
        _setTokenURI(newTokenId, tokenURI);

        // 注冊事件
        emit NFTMinted(newTokenId);

        return newTokenId;
    }

    function getTokenCounter() public view returns (uint256) {
        return _tokenIds.current();
    }
}

上面的代碼中,通過 mintNFT 方法鑄造 NFT,主要有2個引數,第1個引數是接收NFT 的地址,第2個引數是 NFTURL 地址,也就是上文中提到的元資料地址,

3.3、撰寫測驗腳本

在撰寫測驗腳本前,我們先通過 IPFS工具,上傳我們的圖片和元資料檔案,下面是我們已經上傳好的2個元資料檔案:

檔案1,內容如下:

{
    "name": "mshk-logo-black",
    "description": "mshk.top logo black",
    "image": "https://bafybeihodzhbtntgml7t72maxill576ssax6md5kfu72aq4gd4p53oipn4.ipfs.infura-ipfs.io/",
    "attributes": [
        {
            "trait_type": "customAttr",
            "value": 100
        }
    ]
}

檔案2,內容如下:

{
    "name": "mshk-logo-blue",
    "description": "mshk.top logo blue",
    "image": "https://bafybeifxkvzedhwclmibidf5hjoodwqkk2vlbbrlhd3bxbl3wzmkmyrvpq.ipfs.infura-ipfs.io/",
    "attributes": [
        {
            "trait_type": "customAttr",
            "value": 200
        }
    ]
}

進入 hardhat 專案目錄,創建 test/ERC721/01_NFT.js 測驗檔案,內容如下:

const { expect } = require("chai");
const { ethers } = require("hardhat");

/**
 * 運行測驗方法:
 * npx hardhat test test/ERC721/01_NFT.js
 */
describe("NFT MarketPlace Test", () => {


    // NFT 元資料1
    const TOKEN_URI1 = "https://bafybeif5jtlbetjp2nzj64gstexywpp53efr7yynxf4qxtmf5lz6seezia.ipfs.infura-ipfs.io";
    // NFT 元資料2
    const TOKEN_URI2 = "https://bafybeibyb2rdn6raav4ozyxub2r5w4vh3wmw46s6bi54eq7syjzfkmbjn4.ipfs.infura-ipfs.io";

    let owner;
    let addr1;
    let addr2;
    let addrs;

    let nftMarketplaceContractFactory;
    let nftContractFactory;
    let nftMarketplaceContract;
    let nftContract;

    let IDENTITIES;

    beforeEach(async () => {
        [owner, addr1, addr2, ...addrs] = await ethers.getSigners();

        IDENTITIES = {
            [owner.address]: "OWNER",
            [addr1.address]: "DEPLOYER",
            [addr2.address]: "BUYER_1",
        }

        var NFTMarketplaceContractName = "NftMarketplace";
        var NFTContractName = "MSHK721NFT"

        // 獲取 NFTMarketplace 實體
        nftMarketplaceContractFactory = await ethers.getContractFactory(NFTMarketplaceContractName);
        // 部署 NFTMarketplace 合約
        nftMarketplaceContract = await nftMarketplaceContractFactory.deploy()

        // 獲取 nftContract 實體
        nftContractFactory = await ethers.getContractFactory(NFTContractName);
        // 部署 nftContract 合約
        nftContract = await nftContractFactory.deploy()

        console.log(`owner:${owner.address}`)
        console.log(`addr1:${addr1.address}`)
        console.log(`addr2:${addr2.address}`)

        //
        console.log(`${NFTMarketplaceContractName} Token Contract deployed address -> ${nftMarketplaceContract.address}`);

        //
        console.log(`${NFTContractName} Token Contract deployed address -> ${nftContract.address} owner:${await nftContract.owner()}`);

    });

    it("mint and list and buy item", async () => {

        console.log(`Minting NFT for ${addr1.address}`)
        // 為 addr1 鑄造一個 NFT
        let mintTx = await nftContract.connect(owner).mintNFT(addr1.address, TOKEN_URI1)
        let mintTxReceipt = await mintTx.wait(1)


        // 非常量(既不pure也不view)函式的回傳值僅在函式被鏈上呼叫時才可用(即,從這個合約或從另一個合約)
        // 當從鏈下(例如,從 ethers.js 腳本)呼叫此類函式時,需要在交易中執行它,并且回傳值是該交易的哈希值,因為不知道交易何時會被挖掘并添加到區塊鏈中
        // 為了在從鏈下呼叫非常量函式時獲得它的回傳值,可以發出一個包含將要回傳的值的事件
        let tokenId = mintTxReceipt.events[0].args.tokenId


        expect(tokenId).to.equal(1);

        // 授權 市場合約 可以操作這個NFT
        console.log("Approving Marketplace as operator of NFT...")
        let approvalTx = await nftContract
            .connect(addr1)
            .approve(nftMarketplaceContract.address, tokenId)
        await approvalTx.wait(1)

        // NFT交易價格 10 ETH
        let PRICE = ethers.utils.parseEther("10")

        // 將 NFT 加入到串列
        console.log("Listing NFT...")
        let listItemTX = await nftMarketplaceContract
            .connect(addr1)
            .listItem(nftContract.address, tokenId, PRICE)
        await listItemTX.wait(1)
        console.log("NFT Listed with token ID: ", tokenId.toString())

        const mintedBy = await nftContract.ownerOf(tokenId)

        // 檢查 nft 的 owner 是否為 addr1
        expect(mintedBy).to.equal(addr1.address)

        console.log(`NFT with ID ${tokenId} minted and listed by owner ${mintedBy} with identity ${IDENTITIES[mintedBy]}. `)

        //---- Buy 

        // 根據 tokenId 獲取 NFT
        let listing = await nftMarketplaceContract.getListing(nftContract.address, tokenId)
        let price = listing.price.toString()

        // 使用 addr2    從 nftMarketplaceContract 買入 TOKEN_ID 為 0 的NFT
        const buyItemTX = await nftMarketplaceContract
            .connect(addr2)
            .buyItem(nftContract.address, tokenId, {
                value: price,
            })
        await buyItemTX.wait(1)
        console.log("NFT Bought!")

        const newOwner = await nftContract.ownerOf(tokenId)
        console.log(`New owner of Token ID ${tokenId} is ${newOwner} with identity of ${IDENTITIES[newOwner]} `)

        //---- proceeds
        const proceeds = await nftMarketplaceContract.getProceeds(addr1.address)

        const proceedsValue = https://www.cnblogs.com/lion.net/archive/2022/08/05/ethers.utils.formatEther(proceeds.toString())
        console.log(`Seller ${owner.address} has ${proceedsValue} eth!`)

        //---- withdrawProceeds
        const addr1OldBalance = await ethers.provider.getBalance(addr1.address);
        await nftMarketplaceContract.connect(addr1).withdrawProceeds()
        const addr1NewBalance = await ethers.provider.getBalance(addr1.address);
        console.log(`${addr1.address}  old:${ethers.utils.formatEther(addr1OldBalance)} eth,withdrawProceeds After:${ethers.utils.formatEther(addr1NewBalance)} eth!`)

    });


    it("update and cancel nft item", async () => {
        // 為 addr2 鑄造一個 NFT
        let mintTx = await nftContract.connect(owner).mintNFT(addr2.address, TOKEN_URI2)
        let mintTxReceipt = await mintTx.wait(1)


        // 非常量(既不pure也不view)函式的回傳值僅在函式被鏈上呼叫時才可用(即,從這個合約或從另一個合約)
        // 當從鏈下(例如,從 ethers.js 腳本)呼叫此類函式時,需要在交易中執行它,并且回傳值是該交易的哈希值,因為不知道交易何時會被挖掘并添加到區塊鏈中
        // 為了在從鏈下呼叫非常量函式時獲得它的回傳值,可以發出一個包含將要回傳的值的事件
        let tokenId = mintTxReceipt.events[0].args.tokenId

        // 授權 市場合約 可以操作這個NFT
        console.log("Approving Marketplace as operator of NFT...")
        approvalTx = await nftContract.connect(addr2).approve(nftMarketplaceContract.address, tokenId)
        await approvalTx.wait(1)

        // NFT交易價格 0.1 ETH
        PRICE = ethers.utils.parseEther("0.1")

        // 將 NFT 加入到串列
        console.log("Listing NFT...")
        listItemTX = await nftMarketplaceContract.connect(addr2).listItem(nftContract.address, tokenId, PRICE)
        await listItemTX.wait(1)
        console.log("NFT Listed with token ID: ", tokenId.toString())


        console.log(`Updating listing for token ID ${tokenId} with a new price`)

        listing = await nftMarketplaceContract.getListing(nftContract.address, tokenId)
        let oldPrice = listing.price.toString()
        console.log(`oldPrice:  ${ethers.utils.formatEther(oldPrice.toString())}`)

        // 更新價格
        const updateTx = await nftMarketplaceContract.connect(addr2).updateListing(nftContract.address, tokenId, ethers.utils.parseEther("0.5"))

        // 等待鏈上處理
        const updateTxReceipt = await updateTx.wait(1)

        // 從事件中獲取更新的價格
        const updatedPrice = updateTxReceipt.events[0].args.price
        console.log(`updated price:  ${ethers.utils.formatEther(updatedPrice.toString())}`)

        // 獲取資訊,確認價格是否有變更.
        const updatedListing = await nftMarketplaceContract.getListing(
            nftContract.address,
            tokenId
        )
        console.log(`Updated listing has price of ${ethers.utils.formatEther(updatedListing.price.toString())}`)

        //----------cancel
        let tx = await nftMarketplaceContract.connect(addr2).cancelListing(nftContract.address, tokenId)
        await tx.wait(1)
        console.log(`NFT with ID ${tokenId} Canceled...`)

        // Check cancellation.
        const canceledListing = await nftMarketplaceContract.getListing(nftContract.address, tokenId)
        console.log("Seller is Zero Address (i.e no one!)", canceledListing.seller)
    });

});

上面的測驗腳本中,我們分成兩部分,注釋比較詳細,下面是簡要介紹這兩部分測驗的功能,
??第1部分:

  • addr1 用戶鑄1個NFT
  • 授權 NFT市場 可以操作這個 addr1 的 NFT,
  • NFT 加入到 NFT市場,設定價格為 10 ETH,
  • 使用 addr2 用戶購買 addr1 的NFT,
  • 查看addr1NFT市場 的余額
  • NFT市場中的余額取出到 addr1 的余額,對比前后余額資料,

第2部分:

  • addr2 用戶鑄1個NFT
  • 授權 NFT市場 可以操作這個 addr2 的 NFT,
  • NFT 加入到 NFT市場,設定價格為 0.1 ETH,
  • addr2 的NFT價格從 0.1 ETH 更新為 0.5 ETH,進行資料對比輸出,
  • NFT市場 中下架 addr2 的 NFT,

下面是我們運行測驗腳本的效果:
mshk.top

到目前為止,我們已經完成了 NFT 的創建,并將 NFT 加入到市場完成了買、賣、查看銷售后的余額,轉帳給賣家等功能,

專案的原始碼都保存在 Github:https://github.com/idoall/NFT-ERC721-NFTMarketPlace

克隆專案到本地后,進入 hardhat 專案目錄,先執行 yarn install 下載依賴包,

$ yarn install
yarn install v1.22.19
warning package.json: No license field
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
warning hardhat-project: No license field
[1/4] ??  Resolving packages...
[2/4] ??  Fetching packages...
[3/4] ??  Linking dependencies...
warning " > @nomiclabs/[email protected]" has incorrect peer dependency "@nomiclabs/hardhat-ethers@^2.0.0".
warning " > @openzeppelin/[email protected]" has incorrect peer dependency "@nomiclabs/hardhat-ethers@^2.0.0".
warning "hardhat-deploy > [email protected]" has incorrect peer dependency "ethers@~5.5.0".
[4/4] ??  Building fresh packages...
?  Done in 15.42s.

安裝完依賴包后,運行npx hardhat test test/ERC721/01_NFT.js 命令,可以看到和上圖一樣的效果,

$ npx hardhat test test/ERC721/01_NFT.js
Compiled 16 Solidity files successfully


  NFT MarketPlace Test
owner:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
addr1:0x70997970C51812dc3A010C7d01b50e0d17dc79C8
addr2:0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
NftMarketplace Token Contract deployed address -> 0x5FbDB2315678afecb367f032d93F642f64180aa3
MSHK721NFT Token Contract deployed address -> 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 owner:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Minting NFT for 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
Approving Marketplace as operator of NFT...
Listing NFT...
NFT Listed with token ID:  1
NFT with ID 1 minted and listed by owner 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 with identity DEPLOYER.
NFT Bought!
New owner of Token ID 1 is 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC with identity of BUYER_1
Seller 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 has 10.0 eth!
0x70997970C51812dc3A010C7d01b50e0d17dc79C8  old:9999.999797616067546951 eth,withdrawProceeds After:10009.9997570794102017 eth!
    ? mint and list and buy item (232ms)
owner:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
addr1:0x70997970C51812dc3A010C7d01b50e0d17dc79C8
addr2:0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
NftMarketplace Token Contract deployed address -> 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
MSHK721NFT Token Contract deployed address -> 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 owner:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Approving Marketplace as operator of NFT...
Listing NFT...
NFT Listed with token ID:  1
Updating listing for token ID 1 with a new price
oldPrice:  0.1
updated price:  0.5
Updated listing has price of 0.5
NFT with ID 1 Canceled...
Seller is Zero Address (i.e no one!) 0x0000000000000000000000000000000000000000
    ? update and cancel nft item (156ms)


  2 passing (2s)

4、將 NFT 部署到 Rinkeby 網路,在 OpenSea 上查看

打開 hardhat.config.js 檔案,編輯內容如下并保存:

  • 修改里面的 RINKEBY_RPC_URL 為你的地址,如果沒有帳號,可以去 alchemy.com 注冊一個,以后開發區塊鏈時會經常使用到,
  • 修改 PRIVATE_KEY 為你要部署的帳號私鑰,

4.1、部署 NFT市場

運行下面的命令,將 NFT市場 部署到 Rinkeby 網路:

$ npx hardhat run script/ERC721/01-deploy-NftMarketplace.js --network rinkeby
----------------------------------------------------
deployer address -> 0xbB0a92d634D7b9Ac69079ed0e521CC2e0a97c420
NftMarketplace Contract deployed address -> 0x48aD115EE899Cc01d6Fd2Ea9BC3fE5bd7d3E1B1C

Rinkeby 網路,查看我們創建的NFT交易市場合約,效果如下圖:

4.2、部署 NFT 721示例

運行下面的命令,將 NFT示例 部署到 Rinkeby 網路:

$ npx hardhat run script/ERC721/02-deploy-MSHKNFT.js --network rinkeby
----------------------------------------------------
deployer address -> 0xbB0a92d634D7b9Ac69079ed0e521CC2e0a97c420
MSHK721NFT Contract deployed address -> 0x4b241b36D445E46dAE1916f5A0e76dfE470df115
----------------------------------------------------

記住我們創建的合約地址0x4b241b36D445E46dAE1916f5A0e76dfE470df115,后面我們會對合約進行線上驗證,

Rinkeby 網路,查看我們創建的NFT721合約,效果如下圖:

4.3、對 NFT 721示例 合約在 Rinkeby 網路進行驗證

驗證 NFT示例 合約:

$ npx hardhat verify --contract contracts/ERC721/MSHK721NFT.sol:MSHK721NFT 0x4b241b36D445E46dAE1916f5A0e76dfE470df115 --network rinkeby
Nothing to compile
Successfully submitted source code for contract
contracts/ERC721/MSHK721NFT.sol:MSHK721NFT at 0x4b241b36D445E46dAE1916f5A0e76dfE470df115
for verification on the block explorer. Waiting for verification result...

Successfully verified contract MSHK721NFT on Etherscan.
https://rinkeby.etherscan.io/address/0x4b241b36D445E46dAE1916f5A0e76dfE470df115#code

4.4、在 Rinkeby 網路鑄造 NFT

我們打開 Rinkeby 網路,瀏覽剛剛創建的 NFT 721示例 合約,為地址 0x0BFd206c851729590DDAdfCa9439b30aD2AAbf9F 創建一個 NFTNFT 的元資料,使用 IPFS工具創建好的元資料地址 https://bafybeif5jtlbetjp2nzj64gstexywpp53efr7yynxf4qxtmf5lz6seezia.ipfs.infura-ipfs.io

操作步驟如下圖:
mshk.top

創建 NFT 后我們可以通過 交易哈希 看到,NFT合約 0x4b241b36d445e46dae1916f5a0e76dfe470df115,剛剛創建的 Token ID1的 Token,

4.5、在 opensea 查看剛剛鑄造的NFT

瀏覽以下地址 https://testnets.opensea.io/assets/rinkeby/0x4b241b36d445e46dae1916f5a0e76dfe470df115/1 可以看到我們剛剛鑄的NFT 圖片,
在URL部分,rinkeby 表示網路名稱,0x4b241b36d445e46dae1916f5a0e76dfe470df115NFT721 的合約地址,1Token ID
mshk.top

至此,我們完成了如何鑄造NFT,以及完善一個可以買、賣交易的 NFT市場,包括發布到 rinkeby 網路后,在 opensea 測驗網路查看,

如果發布到主網,將 rinkeby 更改為 ethmainnet 即可,

5、專案原始碼

Github:https://github.com/idoall/NFT-ERC721-NFTMarketPlace

6、推薦閱讀

常用詞匯表
??Solidity v0.8.4 Custom Error


轉載宣告:可以轉載, 但必須以超鏈接形式標明文章原始出處和作者資訊及著作權宣告,謝謝合作!


轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/501108.html

標籤:其他

上一篇:軟體測驗基礎理論(2)

下一篇:被面試官一句話點破:3年測驗經驗還不如人家1年的經驗...

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more