iT邦幫忙

2022 iThome 鐵人賽

DAY 15
0
Web 3

Road Map To DApp Developer系列 第 15

【DAY15】 - Dynamic NFT & Oracle (I)

  • 分享至 

  • xImage
  •  

Preface

在這一篇中會介紹 Dynamic NFT (dNFT) 是什麼?它的原理是什麼?它可以如何應用在不同形況中?接下來會介紹如何簡單的實作一個 dNFT

Static NFT

Metadata

一般而言,當 NFT 被鑄造 (mint) 時,它儲存在區塊鏈上的資料其實只是一串 Universal Resource Identifier (URI) 連結而已。通常這串連結會連接到儲存在 InterPlanetary File System (IPFS) (一個分散式且去中心化的資料儲存網路,見 Day6) 上面的 metadata。這串 metadata 是以 json 檔的形式儲存,這份 metadata 中有一串 Uniform Resource Locator
(URL) 中就是實際儲存這個 NFT 的地點,而其他的就是一些關於它的描述。

How to get metadata?

我們拿無聊猿當作例子:
在 opensea 上點入隨便一隻無聊猿,這邊我們拿 BAYC#3030 作為例子,在下方 detail 的部分可以找到 metadata,打開後就可以看到這一串文字。

{
"image":"ipfs://Qmd2VW9uTn1TuG6sP21SGCp41URP2eeyr2A4QhnU82wmyP",
"attributes":
 [
    {"trait_type":"Hat","value":"Cowboy Hat"},
    {"trait_type":"Background","value":"Aquamarine"},
    {"trait_type":"Clothes","value":"Leather Jacket"},
    {"trait_type":"Mouth","value":"Phoneme  ooo"},
    {"trait_type":"Fur","value":"Golden Brown"},
    {"trait_type":"Eyes","value":"Closed"}
 ]
}

複製上面 "image" 中的那串字串,拿去瀏覽器的網址欄貼上,就可以看到真正儲存在 IPFS 裡面的圖檔了。而在 ipfs:// 後面的一串看似亂碼的字串,其實是這張圖片的 metadata 儲存在 ipfs 上後經由 hash 後得到的 CID。除此之外也可以看到 #3030 無聊猿的各種特質,像是眼睛是閉著的、背景是水藍色等。

這種形式 NFT 的 metadata 無法改動 (這是因為合約中已經寫死了),因此被稱為 Static NFT (sNFT)。不過要注意並不是全部的 NFT 都會公布他們的 metadata 在 Opensea 上面,像是 CyperPunk,所以需要用其他的方式來查詢。

或是直接利用昨天 fetch(url) 的方式來得到也可以!

Dynamic NFT

這樣講起來,相較於 sNFT,你可能會以為 dNFT 的 metadata 是可以改變的?這樣說其實也沒有錯,但是他改變的方式並不是在 IPFS 上把 metadata 整個更改。

更精確地來說,dNFT 通常是透過改變智能合約上的 URI 以達到讓 NFT 的 metadata 改變的效果。

而也可以將 NFT 想像成 C 語言中的 pointer 來思考。

  • 每一個 NFT 都有一個專屬的 tokenId,就像是 pointer 它具有一個專屬的變數名稱。
  • pointer 可以透過指向不同的變數來改變它存取的值,NFT 也可以透過改變 metadata 來改變這顆 NFT 所代表的內容。

若原本 一枚 NFT 叫做 x,它原本指向的 metadata 是 M1,若我們透過某些方式讓它指向的方向轉成 M2,便可以達到 dynamic 的效果了!

利用鏈上資料改變而改變 NFT 的方法

這邊來簡單的實作一個可以利用鏈上資訊的改變使 NFT 的 metadata 改變的簡單方法。
首先要做一個 ERC721 的基本合約,繼承所有需要的合約像是 IERC721 等等。 我主要是利用 openzeppelin 中的 contract wizard,選擇 ERC721 mintable, ownable, tokenId automatically increment 等功能。

(這邊的方法是找到網路上一些方法實作的,但可能融合了多個範例)

Baby YoungMan ElderMan

我在 icon 上面找了三個不同時期的 png,分別是 baby、youngman、elderman,而他們的 metadata 格式長這樣:

{
  "name": "Cute Baby",
  "description": "A cute baby.",
  "image": "https://ipfs.io/ipfs/QmWooEZvZaXeS55uLeDVZLMH41QK6q38v9VvgDC3vKWfmD/baby.png", 
  "attributes": [
    {
      "trait-type": "LifeStage",
      "value": "BABY"
    },
    {
      "trait-type": "Hair",
      "value": "BABYHAIR"
    }
  ]
}

接下來會進入合約的部分。

// == 以下為合約部分內容 ==
// 我們首先放入不同階段的 metadata
string[] IpfsURI = [
    "https://ipfs.io/ipfs/QmWooEZvZaXeS55uLeDVZLMH41QK6q38v9VvgDC3vKWfmD/baby.json",
    "https://ipfs.io/ipfs/QmWooEZvZaXeS55uLeDVZLMH41QK6q38v9VvgDC3vKWfmD/youngman.json",
    "https://ipfs.io/ipfs/QmWooEZvZaXeS55uLeDVZLMH41QK6q38v9VvgDC3vKWfmD/elderman.json"
];
// mint 後會得到第一階段的 NFT
function safeMint(address to) public onlyOwner {
    uint256 tokenId = _tokenIdCounter.current();
    _tokenIdCounter.increment();
    _safeMint(to, tokenId);
    _setTokenURI(tokenId, ipfsURI[0]);
}

按照上面的邏輯,我把這個 NFT contract 中其中幾個部分做了調整,首先將 NFT 的 metadata 的 URI 分成三個階段,依序存在 ipfsURI 裡面,接下來在 safeMint() 中,把每次 mint 完的 metadata 設為 ipfsURI[0]

// global variable
uint current_age = 0;
// 設定一個增加年齡的 function 
function grow(uint age) public {
    current_age += age;
    if (current_age >= 30){growStage(0);}
    else if (current_age >= 60){growStage(0);}
}

function growStage(uint256 tokenId) public {
    if (checkStage(tokenId) >= 2) {return;}
    uint256 newStage = checkStage(tokenId) + 1;
    string memory newUri = ipfsURI[newStage];
    _setTokenURI(tokenId, newUri);

}

function checkStage(uint256 tokenId) public view returns(uint256 stage) {
    string memory _uri = tokenURI(tokenId); // 取出這一枚 token 的 URI
    // 利用 hash() 來比較 uri 是否相同
    bytes32 _hashUri = keccak256(abi.encodePacked(_uri));
    bytes32 _ipfsUri0 = keccak256(abi.encodePacked(ipfsURI[0]));
    bytes32 _ipfsUri1 = keccak256(abi.encodePacked(ipfsURI[1]));
    bytes32 _ipfsUri2 = keccak256(abi.encodePacked(ipfsURI[2]));
    // baby
    if (_hashUri == _ipfsUri0) {return 0;}
    // youngman
    else if (_hashUri == _ipfsUri1) {return 1;}
    // elderman
    else if(_hashUri == _ipfsUri2) {return 2;}
}

接下來就是實作 dynamic NFT 的邏輯。

  1. 首先 checkStage() 會利用 keccak256() 的函式來比較 URI 是否是相同(因為 solidity 裡面不能直接比較 string 啊 QQ),依序回傳 0、1、2,代表著目前這個 NFT 處於何種 stage。
  2. 接下來 growStage(),就是利用 checkStage() 取得目前 stage 後,可以用來升級 stage。而若已經是第三階段則直接 return
  3. 最後是用來手動增加年齡,並同時判斷是否會改變的 grow()。我設定超過 30 歲時,baby 會變成 youngman,而超過 60 歲後就會變成 elderman。

因此要玩這一段程式碼的順序就是:

  1. safeMint() 到你的 address。

  2. 查看 tokenURI(0) 的 URI 地址。可以看到下圖:

  3. grow(輸入你想要增加的年齡)

  4. 查看 checkStage() 或是 tokenURI() 可以看到 stage 還有 URI 的變化。

Closing

Dynamic NFT 算是嶄新的一類 NFT,或有人稱之為 Interactive NFT,它不再只是一張靜止於鏈上的圖片,而是可以與使用者互動,產生不同玩法及應用價值的新一代 NFT。也有許多人視 dNFT 為 NFT 的未來,稱之為 NFT 2.0!在下一篇中會提到更多關於 dNFT 的應用,也同時會實作一些相關的內容!

References


若有文章內有任何錯誤的地方歡迎指點與討論!非常感謝!

歡迎贊助窮困潦倒大學生
0xd8538ea74825080c0c80B9B175f57e91Ff885Cb4


上一篇
【DAY14】 - Fetch Data From Opensea
下一篇
【DAY16】 - Dynamic NFTs & Oracle (Chainlink Oracle)
系列文
Road Map To DApp Developer30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言