iT邦幫忙

2021 iThome 鐵人賽

DAY 26
0
Modern Web

All In One NFT Website Development系列 第 26

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


上一篇
Day 25【Deploy NFT - Layers Blending & MetaData】Read the License
下一篇
Day 27【Deploy NFT - Deploy on Testnet】Hey Listen, I QUIT!!
系列文
All In One NFT Website Development30

尚未有邦友留言

立即登入留言