Day 26【Deploy NFT - Lazy-Minting & Smart Contract】Right Click and Save Image As

Untitled

【前言】
接下来我们要进到整个 Project 重头戏中的重头戏啦,当我们都具备好图档以及 MetaData 之後,接下来就是上链工程了!

【Lazy-Minting】
首先我们要来介绍我们让顾客 Mint NFT 的方法:Lazy-Minting

所谓的 Mint 其实就是顾客进来挖掘我们的 NFT,在这个 Project 里面跟大部分的 NFT 艺术品很像,他们并不知道自己会挖掘到什麽。而 Mint 的过程顾客除了会支付我们(创作者)一笔费用(NFT 的价格)外还需要向矿工支付一笔手续费(GAS)。

那为什麽要使用 Lazy Mint 呢?因为如果是我们(创作者)先 Mint 出 10000 个 NFT 再卖给客人,那等於我们要先支付挖掘这一万个 NFT 的 GAS!可想而知这是一个很大笔的费用。所以我们会利用 Lazy-Mint 的方法,在这个 NFT 被 Mint 的时候把 GAS 转嫁到消费者身上。

这边同时我还要介绍一下,除了单纯把商品陈列上 Opensea 等卖场的公开招标方式之外,还有另外一种贩卖 NFT 的方法叫做:Loot-Box。就像我们平常玩线上游戏时抽的卡包或角色卡牌,在抽之前我们并不知道会抽到什麽对吧!如果是用 Lazy Mint 的方式也能做出类似 Loot-Box 的功能!

而在 Opensea 的 Developer Doc. 之中,他们示范的 Loot-Box 实作是在智能合约中先有一个称为 Loot-Box 的物件,以及一个名为 unpack() 的函式。在顾客买了之後需要在我们的官网之类的地方有一个「开箱」的功能,来触发 unpack()!举例来说就是顾客在我们的网站 Mint 了一颗「恐龙蛋」,也就是一种 Loot-Box 的物件。而他并不知道蛋里面是什麽。需要在我们官网或其他地方找到一个「孵化器」,来将这颗蛋「孵化」,最後智能合约会 Burn 这个「蛋」,并且产出一个真正的「恐龙」(Real NFT)。

但不知道,某些人觉得蛮有趣的,但对我这种穷人来说还蛮麻烦的,因为我还需要再付一次 gas 的费用。

qjWA7Mj.jpg

但因为我们不打算实作这个功能,就单纯做 Lazy-Mint 就能做到在顾客购买之前,NFT 是处於盲盒的状态。除了有惊喜感之外,也能确保公平性。就是在 Initial Launch 的时候,大家花一样的钱就拥有一样的机率抽到比较稀有的 NFT!

【Structuring Smart Contract - Basic Setting】
首先打开我们的 Remix IDE!然後进入魔幻的区块链世界!

以下就是智能合约的基本设置,我有打上一些注解大家可以详细的看!关於 baseURI 的部分我们明天里用到 IPFS 储存资料时我会详述!

// SPDX-License-Identifier: GPL-3.0
// Latest Revised 2021/9/16 10:08
// Specially thanks to HashLips!!

pragma solidity ^0.8.0; // compiler version

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

contract TheDin is ERC721Enumerable, Ownable {
  using Strings for uint256;

  string public baseURI; // The Base URL where to store the data and image of the NFT
  string public baseExtension = ".json";
  uint256 public cost = 0.06 ether; // How much to mint the NFT
  uint256 public maxSupply = 10000; // TOTAL SUPPLY FUNALLY
  uint256 public maxMintAmount = 20; // Everytime the max quantity a customer minting the NFT

  constructor(
    string memory _name, //TheDino
    string memory _symbol, // DNMS
    string memory _initBaseURI 
  ) ERC721(_name, _symbol) {
    setBaseURI(_initBaseURI); // ipfs://..../
    mint(msg.sender, 10); // 这里先挖十个当作测试
  }

  // internal
  function _baseURI() internal view virtual override returns (string memory) {
    return baseURI;
  }

【如何预留】
首先我遇到的第一个问题就是:如何预留。也就是说有时候我们想要预留 10 个 NFT 当作之後空投的奖励,或者是要拿来送人该怎麽做呢?我目前想到的方法有两个:

第一个是写在 constructer 里面,也就是像上面的程序码一样,mint(msg.sender, 10) 先挖 10 个保存起来,而因为在建置合约的时候是用我们的帐号,那 msg.sender 就是 Mint NFT 的人也就是我们!。第二个方法就是放上网页之後,在开放给大家 MINT 之前先用手动直接 MINT 10 个,不过这个方法有点土法炼钢。

好消息是不管是写在 constructer 里面还是先偷 MINT,这两个方法是可以并存的!

【如何自订 tokenID
再来我们遇到第二个难题,也就是如何挖掘自订的 tokenID 呢?因为企划里面预计要留存五个客制化的恐龙给 Dino 的创始人,所以会需要这个功能。

我在 OpenZeppelin 的文件找到了 _safeMint(address to, uint256 tokenId) 这个好东西!也就是说我们可以在 constructer 里面不只预留,还可以挖掘自订的 tokenID 呢!

	constructor(
	    ...
	  ) ERC721(_name, _symbol) {
	    ...
	    _safeMint(msg.sender, 102);
	    _safeMint(msg.sender, 318);
	    _safeMint(msg.sender, 713);
	    _safeMint(msg.sender, 930);
	    _safeMint(msg.sender, 516);
	  }

ERC 721 - OpenZeppelin Docs

【Structuring Smart Contract - Minting Time】
但我们依然要让普通的消费者从 0 号开始 Mint,也就是说我们还要写一个正常普通的 mint(address to) 让他们在官网上做 MINT!

首先就是一些基本的判断,确保我们有足够的数量给消费者 MINT,以及消费者有足够的金额。再来就是要跑一个回圈,因为消费者一次最多可以 MINT 20 个 NFT,那我们就要跑 _safeMint() 20 次。但因为我们有先 MINT 五个特定编号的 NFT 了,所以为了避免发生错误,利用回圈以及 _exists() 的功能来跳过已经存在的 tokenID

  // public
  function mint(address _to, uint256 _mintAmount) public payable {
    uint256 supply = totalSupply() - 5;
		// 当前发行量,这边 - 5 是因为我们下一个 mint 的是第十一个
    require(_mintAmount > 0); // 每次必须挖超过 0 个
    require(_mintAmount <= maxMintAmount); // 挖的数量不可以大於每次最大挖掘数量
    require(supply + _mintAmount <= maxSupply);
		// 挖的数量和当前发行量加起来,不可以超过最大总发行量

    for (uint256 i = 0; i < _mintAmount; i++) { // tokenID 从 0 开始
			while(_exists(supply + i)){
            i++;
      }
      _safeMint(_to, supply + i); // 用回圈来挖
    }
  }

【Structuring Smart Contract - Useful Function】
这边再多加两个有用的函式,非常好理解!

  • walletOfOwner(address _owner) 是可以查询这个 owner 持有的所有我们的 NFT!主要是利用 balanceOf(_owner) 来看出他有多少资产,然後再利用 tokenOfOwnerByIndex(_owner, _index) 来一个一个调阅出来!
  // public
  function walletOfOwner(address _owner)
    public
    view
    returns (uint256[] memory)
  {
    uint256 ownerTokenCount = balanceOf(_owner);
    uint256[] memory tokenIds = new uint256[](ownerTokenCount);
    for (uint256 i; i < ownerTokenCount; i++) {
      tokenIds[i] = tokenOfOwnerByIndex(_owner, i);
    }
    return tokenIds;
  }
  • tokenURI(uint256 tokenId) 是可以查询当前这个 NFT 的 tokenURI 是多少,也就是取得 MetaData 的方式!而 Return 的字串就是 BaseURI 再加上 tokenID 编码後的成果,也会是最後储存的网址。
	function tokenURI(uint256 tokenId)
    public
    view
    virtual
    override
    returns (string memory)
  {
    require(
      _exists(tokenId),
      "ERC721Metadata: URI query for nonexistent token"
    );

    string memory currentBaseURI = _baseURI();
    return bytes(currentBaseURI).length > 0
        ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension))
        : "";
  }

【Structuring Smart Contract - Owner Function】
接下来就是一些合约持有者的好用函式,主要是替修改合约起到一个接口的作用。像是重新设定价格,重新设定单次可以 Mint 的数量,重新设定 BaseURI(当我们转换存放的资料库时可能会用到),暂停合约执行等。

	//only owner
	function setCost(uint256 _newCost) public onlyOwner() {
	  cost = _newCost;
	}
	
	function setmaxMintAmount(uint256 _newmaxMintAmount) public onlyOwner() {
	  maxMintAmount = _newmaxMintAmount;
	}
	
	function setBaseURI(string memory _newBaseURI) public onlyOwner {
	  baseURI = _newBaseURI;
	}
	
	function pause(bool _state) public onlyOwner {
	  paused = _state;
	}

【Deploy Smart Contract on Rinkeby Testnet】
首先让我们编译并且在 injected Web3 连接到 MetaMask 之後执行合约。那我们要先在 Rinkeby Testnet 的情况下执行。在测试网上可以藉由某些方法获得免费的以太币,毕竟在区块链上犯错要修正是需要很大的成本,所以要多多利用 Testnet 先行测试。

图片 1.png

图片 2.png

How to get ETH for Rinkeby Testnet (Closed Beta)

之後选择正确的合约,将 Project Name 以及 Symbol 填入後(INTBASEURI 因为我们还没将资料上传,明天再详述!),按下 Deploy,MetaMask 就会跳出交易的核准同意啦!

图片 3.png

图片 4.png

图片 5.png

确认之後过大概十秒钟就可以看到合约成功被 Deploy 了!此时我们复制好智能合约的地址,并且到Etherscan Rinkeby Testnet Explorer 来把自己的地址贴上去查询!

rinkeby.etherscan

图片 6.png

对,这就是我的地址!如果按下 Tracker 进去看,也是 15,而且编号都跟我要的一模一样!那我们回到 Remix 里面看看我们的 totalSupply!对,还是 15!

图片 7.png

图片 8.png

接下来兴奋的事情要来了,我们来到 Opensea的 testnet。

testnets.opensea

惊喜的事情发生了,不只有我们原本 MINT 的十个 NFT。还有我自订编号的五个 NFT!

图片 9.png

到这里今天的内容就结束啦,如果大家想知道为什麽没有图片的话,明天可以继续关注我的贴文ㄚ!上架的任务可是还没结束呢!

【小结】
感觉这篇会是我三十天里面最有价值的一篇,因为网路上中文的资料是趋近於零,然後英文的资料也少到不能再少。如果说我在这个 Project 中做了什麽,可以说我是在不断疯狂的 Google。所以大家可能会发现下面的参考资料有超级多连结,真的要再次感谢这些善良的大神…否则我的小脑袋实在是无法负荷阿。

216868988_4121299491252231_2198183256717365593_n.jpg

【参考资料 - Lazy Minting】
What is Lazy Minting in Ethereum?
Lazy minting
nft-website/lazy-minting.md at main · protocol/nft-website
Building an NFT Merkle Airdrop - OpenZeppelin blog

【参考资料 - Loot Box】
GitHub - pooltogether/loot-box: Loot Boxes are Ethereum smart contracts that can be traded like NFTs and plundered by the owner
Making a Cypherpunk Loot Derivative in < 15 minutes using Remix & Solidity

【参考资料 - Structuring Smart Contract】
Custom TokenID with ERC721 OpenZeppelin Preset
NFT/ERC-721/Collectible END-TO-END TUTORIAL | Deploy, List on Opensea, Host Metadata on IPFS
Create an NFT and deploy to a public testnet, using Remix
How To Create NFTs With Solidity
How to Code a Crypto Collectible: ERC-721 NFT Tutorial (Ethereum)
How to create and deploy an ERC-721 (NFT)
Maksim Ivanov
How to Create Own NFT (Using Moralis) - Smart Contract Programming

【开源资料 及 参考 NFT Project】
Avastars | Teleporter


<<:  今晚来聊聊铼德2349

>>:  【Day 26】C String - Practice 1

Day1 参加职训(机器学习与资料分析工程师培训班),记录学习内容(6/30-8/20)

人工智慧与机器学习概论 第一天,早上介绍AI相关的产业,以及目前应用的领域有哪些,例如:AIot智慧...

Day20-Kubernetes 那些事 - ConfigMap 与 Secrets

前言 今天要来介绍两个蛮重要的观念:ConfigMap 以及 Secrets,通常在本机端练习可能比...

Go Concurrency Patterns: Pipelines and cancellation

这篇其实是在Go官方网站的一篇关於并发的解决方案 不过文章有点旧了,是在2014写的 要自己去翻可能...

RISC-V on Rust 从零开始(8) - 实作instruction decoder

这次要来实作指令decoder,负责pipeline中的decode stage。计组教科书上常见的...

Chapter4 附录 贝兹曲线

前言 什麽是贝兹曲线?它能创造一连串平滑的曲线,被应用在PS和AI中的钢笔、以及常见的CSS Ani...