初遇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 的自动验证合约。


                      新年快乐