初遇NFT-IPFS

  • 2021 年 12 月 31 日
  • 筆記

初遇NFT-IPFS

              本次學習如何使用Hardhat框架製作可預售的NFT並利用IPFS存儲元數據。

  1. NFT簡介

    1. NFT全稱Non-fungible Token(即非同質化通證)。不可分割性(目前有碎片化協議暫且不談)且獨一無二。這有點對應現實資產比如古董、藝術品。即使是同一批廠家或者藝術家創作也難保證一模一樣。

    2. NFT 目前主要用於藝術品、域名、虛擬資產、遊戲。代表性的就是CryptoKitties、CryptoPunk、NBA Top Shot等
    3. NFT的價值就是仁者見仁智者見智了,稀有程度,品牌價值,認可度等都是維持價格的因素。------情懷、藝術無價。
    4. 常用的NFT工具:
      1.   交易網站Opensea://opensea.io/
      2.  市場數據分析:歐科雲鏈 //www.oklink.com/zh-cn/nft  NFT流行應用//degendata.io/
      3. NFT應用數據:Dappradder //degendata.io/,給使用過的用戶發過空投價值幾百刀
      4. 重點推薦NFTSCAN  //nftscan.com/  可以看到鏈上NFT的交易情況,也算挖掘最新NFT的常用工具
  2.  開發工具    

    1. hardhat :在以太坊上構建的開發環境。它幫助開發人員管理和自動化構建智慧合約和 dApp 。需要先安裝Node.js。Node.js安裝教程 //blog.csdn.net/fangchunyang/article/details/118178742

    2. ipfs:InterPlanetary File System 星際文件系統。IPFS是一種內容可定址、版本化、點對點超媒體的分散式存儲、傳輸協議。在IPFS的網路里,是根據內容定址,每一個‍‍上傳到IPFS上面去的文件、文件夾,都是以Qm為開頭字母的哈希值,無需知道文件存儲在哪裡,通過哈希值就能夠找到這個文件,這種方式叫內容定址。ipfs教程可以看 tiny熊大佬的附上傳送門link。還需要使用Pinata 有免費版,這個可以上傳文件。附上鏈接//app.pinata.cloud/ 如果想在chrome訪問ipfs 需要安裝插件。附上鏈接  下載

    3. Remix :Remix是以太坊智慧合約程式語言Solidity的一個基於瀏覽器的IDE。附上鏈接//remix.ethereum.org/

  3.           教程開始

    1.   默認已經安裝配置好npm了如果沒有請百度後按照。安裝hardhat 並初始項目          
      1. 1 mkdir nft_1
        2 cd nft_1
        3 npm init -y
        4 npm install --save-dev hardhat

        運行hardhat

        npx hardhat

        我們選擇:basic sample project  然後全程默認回車

        1. 創建好的項目目錄:

            文件contracts 是存放我們寫的智慧合約
            node_modules 是安裝的一些插件
             scripts  是部署合約調用合約
           test 是編寫測試腳本

        2. 現在來為hardhat 安裝一些插件。
          安裝waffle 、ethers  chai web3等插件
          npm install --save-dev @nomiclabs/hardhat-ethers ethers @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-web3 web3 安裝openzeppelin 合約 npm install @openzeppelin/contracts 安裝驗證etherscan插件 npm install --save-dev @nomiclabs/hardhat-etherscan 為了安全 使用.env npm install dotenv

        3. 插件安裝完成後我們需要在項目里添加。

          1.   找到文件 hardhat.config.js 編輯 然後添加我們剛才安裝的插件
          2. require("@nomiclabs/hardhat-waffle");
            require("@nomiclabs/hardhat-web3");
            require("@nomiclabs/hardhat-ethers");
            require("@nomiclabs/hardhat-etherscan");
            require('dotenv').config();

            結果如圖所示

          3. 現在來清理一些不要的文件,
            1.   刪除contracts文件下的Greeter.sol
            2. 刪除test文件下的sample-test.js
    2.       文件配置已經完成 接下來我們來編寫智慧合約
      1.   在contracts 中新建一個合約 MyNFT.sol
        1. 程式碼
          //編譯器版本
          pragma solidity ^0.8.0;
          
          
          //導入openzeppelin庫
          //Ownable 是openzeppelin實現管理員
          import '@openzeppelin/contracts/access/Ownable.sol';
          //ERC721是NFT常用的標準協議
          import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
          //可以枚舉
          import '@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol';
          
          //我們創建的合約需要繼承上面的合約
          contract MyNFT is ERC721, ERC721Enumerable, Ownable {
          
          //開啟售賣nft
              bool public _isSaleActive = false;
          
              // Constants
          //設置NFT數量
              uint256 constant public MAX_SUPPLY = 2048;
          //售賣NFT的價格
              uint256 public mintPrice = 0.01 ether;
          //賬戶最多擁有的NFT數量
              uint256 public maxBalance = 6;
          //賬戶一次性最多鑄造的數量
              uint256 public maxMint = 3;
          
          //基本url
              string private _baseURIExtended;
          
              event TokenMinted(uint256 supply);
              event SaleStarted();
              event SalePaused();
              event AuctionStarted();
              event AuctionPaused();
          
              constructor() ERC721('MoonStart', 'ms') {}
          
              //開始預售
              function startSale() public onlyOwner {
                  _isSaleActive = true;
                  emit SaleStarted();
              }
              //暫停預售
              function pauseSale() public onlyOwner {
                  _isSaleActive = false;
                  emit SalePaused();
              }
          
            //設置價格
              function setMintPrice(uint256 _mintPrice) public onlyOwner {
                  mintPrice = _mintPrice;
              }
          
          //設置擁有NFT數量
              function setMaxBalance(uint256 _maxBalance) public onlyOwner {
                  maxBalance = _maxBalance;
              }
          //設置最大mint數量
              function setMaxMint(uint256 _maxMint) public onlyOwner {
                  maxMint = _maxMint;
              }
          
          //提取合約中的ETH
              function withdraw(address to) public onlyOwner {
                  uint256 balance = address(this).balance;
                  payable(to).transfer(balance);
              }
          
          //返回當前總量
              function getTotalSupply() public view returns (uint256) {
                  return totalSupply();
              }
          //獲取地址擁有的nft
              function getMoonStarByOwner(address _owner) public view returns (uint256[] memory) {
                  uint256 tokenCount = balanceOf(_owner);
                  uint256[] memory tokenIds = new uint256[](tokenCount);
                  for (uint256 i; i < tokenCount; i++) {
                      tokenIds[i] = tokenOfOwnerByIndex(_owner, i);
                  }
                  return tokenIds;
              }
          
          //mint NFT
              function mintMoonStar(uint numMoonStars) public payable {
                  require(_isSaleActive, 'Sale must be active to mint MoonStars');
                  require(totalSupply() + numMoonStars <= MAX_SUPPLY, 'Sale would exceed max supply');
                  require(balanceOf(msg.sender) + numMoonStars <= maxBalance, 'Sale would exceed max balance');
                  require(numMoonStars <= maxMint, 'Sale would exceed max mint');
                  require(numMoonStars * mintPrice <= msg.value, 'Not enough ether sent');
                  _mintMoonStar(numMoonStars, msg.sender);
                  emit TokenMinted(totalSupply());
              }
          
          //mint 函數具體實現
              function _mintMoonStar(uint256 numMoonStars, address recipient) internal {
                  uint256 supply = totalSupply();
                  for (uint256 i = 0; i < numMoonStars; i++) {
                      _safeMint(recipient, supply + i);
                  }
              }
          //設置基本url  主要後面拼接
              function setBaseURI(string memory baseURI_) external onlyOwner {
                  _baseURIExtended = baseURI_;
              }
          //獲取基本url
              function _baseURI() internal view virtual override returns (string memory) {
                  return _baseURIExtended;
              }
          //得到nft的url
              function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
                  require(_exists(tokenId), 'ERC721Metadata: URI query for nonexistent token');
                  return string(abi.encodePacked(_baseURI(), tokenId.toString()));
              }
          //轉移這裡使用了hooks
              function _beforeTokenTransfer(
                  address from,
                  address to,
                  uint256 tokenId
              ) internal override(ERC721, ERC721Enumerable) {
                  super._beforeTokenTransfer(from, to, tokenId);
              }
          // IERC165 介面的實現
              function supportsInterface(bytes4 interfaceId)
              public
              view
              override(ERC721, ERC721Enumerable)
              returns (bool)
              {
                  return super.supportsInterface(interfaceId);
              }
          }
          MyNFT.sol
      2.    在scripts文件創建run.js
        1.  程式碼 
          const main = async () => {
              const nftContractFactory = await hre.ethers.getContractFactory('MyNFT');
              const nftContract = await nftContractFactory.deploy();
              await nftContract.deployed();
              console.log("Contract deployed to:", nftContract.address);
          
            
          };
          
          const runMain = async () => {
              try {
                  await main();
                  process.exit(0);
              } catch (error) {
                  console.log(error);
                  process.exit(1);
              }
          };
          
          runMain();
          View Code

          由於我喜歡在remix 調試,這裡就只用部署合約,沒有進行函數調用

        2. 我們直接使用hardhat的測試網先試著編譯運行一次。
          npx hardhat run scripts/run.js

           無報錯,智慧合約沒問題。

    3.         現在我們來部署在Rinkeby鏈上,因為它有opensea來顯示我們的NFT。如果測試網沒有eth,可以去水龍頭領,我常用的是chainlink的 //faucets.chain.link/rinkeby 很好用,就是每次量少了一些,多點幾次。
      1.   修改hardhat.config.js
        1.   初始的module.exports
          module.exports = {
            solidity: "0.8.4",
          };

          修改完成的是

          module.exports = {
            solidity: "0.8.4",
            networks: {
              rinkeby: {
                url:process.env.STAGING_ALCHEMY_KEY,
                accounts:[process.env.PRIVATE_KEY],
              },
            },
          etherscan: {
          apiKey:process.env.API_KEY
          }
          };
          1. url:我們使用節點才能和智慧合約交互,市面上有很多節點 常用的infura //infura.io/,  alchemy //www.alchemy.com/ 我這裡使用的alchemy,只需要註冊賬戶、新建項目然後複製網站給我們的key 格式差不多是 //eth-rinkeby.alchemyapi.io/v2/xxxxxxxxxxxxx
          2. accounts 這裡需要提供部賬戶的私鑰.為了安全我們使用了env  
          3. apiKey:  這是為了將我們的智慧合約通過etherscan的驗證 ,需要註冊一個賬戶 並且複製apikey  //etherscan.io
      2. 創建.env
        1.   內容    url    STAGING_ALCHEMY_KEY="你的key //eth-rinkeby.alchemyapi.io/v2/xxxxxxxxxxxxx"
        2.  私鑰 PRIVATE_KEY='xxxxxxx'
        3. API_KEY='xxxxx'
      3. hardhat 利用 hardhat-etherscan 插件自動在etherscan 驗證合約失敗,大概是網路問題但我科 學也失敗了,有解決辦法的老哥可以交流下。因此我們需要手動去etherscan 驗證合約。後面寫一篇如何怎麼在etherscan 驗證合約


        1.   首先我們在rineby 部署合約,
          1.   
            npx hardhat run scripts/run.js --network rinkeby

             上面的0xe4 就是合約地址,部署成功,我們現在打開remix   //remix.ethereum.org/

          2. 在remix中新建一個合約,粘貼智慧合約程式碼 MyNFT.sol
              1.  點擊左邊欄的 Deploy & run transactions 在ENVIRIONMENT 選擇Injected Web3  這個可以讓remix 鏈接我們的小狐狸錢包,然後再CONTRACT 找到 MyNFT -contracts/MyNFT.sol接著下面 At Address 粘貼我們上面得到的合約地址,然後點擊 At Address。

              2. 現在我們來交互我們的智慧合約, 點擊箭頭部分,再找到startsale 必須使用部署這個合約的帳號

                1.   先開啟  售賣  

                2.  我們再來mint 1個NFT,此時需要我們發送eth。由於在remix 無法直接填寫0.01ether,需要單位換成'wei' 這是最小單位10000000000000000 wei==0.01ether  1ether==10^18 wei。我這裡演示mint  2個

                   看鏈上返回結果,已經成功。

        2.   現在是NFT已經鑄造好了,但是沒有NFT的一些資訊。NFT的資訊我們成為元數據 他是一個json 格式 我們通過函數 tokenURI 讓我們的NFT加上元數據
          {
              "name": "MoonStart",
              "description": "test NFT",
              "attributes": [
                  {
                      "trait_type": "Background",
                      "value": "Galaxy"
                  },
                  {
                      "trait_type": "color",
                      "value": "blue"
                  }
                  ],
              "image": "//ipfs.io/ipfs/xxx/1.png"
          }
        3. 我們先新建一個文件夾存放我們的圖片,圖片名字  1.jpg ....然後上傳到ipfs.ipfs我們可以使用pinata 網站 //app.pinata.cloud/

           就像這樣,我們需要複製CID,待會添加到json里

        4. 現在來新建文件 來存放元數據   image 哪裡填寫我們剛剛上傳的圖片的 CID。

           然後又通過pinata上傳

           然後複製CID,返回remix中 調用函數 setbaseURI

            這裡我們填寫的是 ipfs://Qmxxxx/

        5. 然後我們耐心等待一段時間,打開opensea的測試網:  //testnets.opensea.io/

          未完待續---------散花 

      4. 我是等了很久,如果pinata 太慢了 建議大家下個ipfs桌面版。

         


         在驗證合約這裡折騰了很久,但也學習到了很多。對應這種導入了第三方庫的合約 解決辦法就是通過 flattener。remix-ide本地版本有這個插件 flattener  ,或者使用truffle-flattener把這些合約全都壓縮在一個文件 並刪除多餘的spdx。最佳的方式還是使用插件來自動化驗證比如 hardhat  truffle 的自動驗證合約。


                      新年快樂

Tags: