# 利用chainlink建立dNFT # 什麼是dNFT? dNFT是指會在特定條件下不斷變化的NFT,相較一般的NFT在鑄造後,URI就是固定的,且背後的URI通常連接IPFS,意即其內容無法變更 而dNFT則是我們會在合約內編寫某個條件或規則,一旦這個條件成立,就會觸發某些功能,例如改變dNFT的URI,或其中的某些數值。 然而在實作上,如何去觸發是一個問題,手動是一個可行但不夠聰明的辦法,較為可行的方式是建立一個server,讓這個server去定期監測contract的條件有沒有成立,再決定要不要觸發,但是這樣的問題就是function只能由server那邊做觸發,整個架構就會趨於中心化。 於是目前較為普遍的做法是採用chainlink幫忙完成觸發,chainlink是一個oracle,負責鏈上與鏈下的資料交互,而這種可以讓chainlink幫忙觸發的功能又被稱為**Automation** 在製作dNFT的時候,我們可以選擇兩種觸發方式,**一種是Time-based Automation**,另一種則是**Custom Logic Automation** ## 前置準備 此實作需要先行部署欲觸發的合約,我這邊都用Remix做舉例,因此需要對Remix有過使用經驗 我們需要先產生一個Upkeep,每個Upkeep來負責觸發一個合約 接下來的操作都是在sepolia testnet上進行,請確定自己在做以下操作是不是都在正確的鏈上 1. 先前往[Chainlink Faucet](https://faucets.chain.link/) 領取測試幣 > 這邊會要求你連接推特才能領取測試幣 2. 如果成功的話你的錢包應該會有0.1test ETH跟20 test LINK,接著再前往[Chainlink Automation](https://automation.chain.link/) > Metamask 需要新增LINK token才能看到你擁有的LINK數量,Sepolia LINK address: 0x779877A7B0D9E8603169DdbD7836e478b4624789,如果在faucet領取上遇到問題可以查看官方頁面 [Acquire testnet LINK](https://docs.chain.link/resources/acquire-link) 3. 連接右上角連接錢包 4. 點選**Register new Upkeep** 來產生一個新的Upkeep ## Time-based Time-based的Upkeep可以設定固定的觸發時間,例如每三分鐘就固定觸發合約中的某一function 在開始之前**請先部署**你要觸發的合約,這邊提供範例,你可以將其複製到Remix,並部署上Sepolia ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; contract Counter { uint public counter; constructor(){ counter = 0; } function count() external { counter = counter + 1; } } ``` 1. 進入Register new Upkeep後,選擇Time-based ![](https://i.imgur.com/GSeNALr.png) 2. 輸入你想要觸發的contract address,點選Next > 這個contract必須先部署在Sepolia上 3. 輸入contract的ABI(若你是從Remix上部署的,可以去Compile介面底下複製ABI) 4. 選擇你想要觸發的function,若你是使用上方的範例合約的話,我們這邊選擇`count()`這個function 5. 輸入你想要觸發的時間,這邊用CRON表示法輸入,或者你可以選用底下的模板 6. 輸入Upkeep的細節設定,這邊可以設定起始資金、GasLimit、Upkeep name等等 > 起始資金這邊可以多放一些,以免造成Upkeep資金過少無法觸發的狀況 7. 點選**Register Upkeep**按鈕 8. 完成 ![](https://i.imgur.com/zsgHAV9.png) 如果成功的話在Upkeep頁面底下的History可以看到Activity type為**Perform Upkeep**的transaction被送出(查看圖片左邊的時間,可以發現我是設定3分鐘觸發一次),並且合約的`counter`不斷地加一 ## Custom Logic Custom Logic的觸發方式為,讓用戶去設定**滿足某個條件的時候才觸發合約**的function,但這邊要特別注意的地方是,這個合約必須是**Compatible Contracts**(具有兼容性的合約),講白話一點就是這個被觸發的這個合約必須遵守他們的某些規範,才可以被正常運作 這個規範就是,合約必須繼承`AutomationCompatibleInterface`,藉此確保合約內有`checkUpkeep`跟`performUpkeep`兩個function `checkUpkeep`是一個view function,目的是用來判斷條件是否達成,如果條件成立就return true,因此用戶可以在這個function裡頭編寫自己的條件,而chainlink那邊會在每個block都去調用`checkUpkeep`,如果回傳的結果是true的話,就執行`performUpkeep` `performUpkeep`這個function裡頭則編寫你想要去觸發的動作 下面是官方提供的範例(可以在[Creating Compatible Contracts](https://docs.chain.link/chainlink-automation/compatible-contracts/)文件中點選Open in Remix來快速開啟跟方便後續部署) ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; // AutomationCompatible.sol imports the functions from both ./AutomationBase.sol and // ./interfaces/AutomationCompatibleInterface.sol import "@chainlink/contracts/src/v0.8/AutomationCompatible.sol"; /** * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ contract Counter is AutomationCompatibleInterface { /** * Public counter variable */ uint public counter; /** * Use an interval in seconds and a timestamp to slow execution of Upkeep */ uint public immutable interval; uint public lastTimeStamp; constructor(uint updateInterval) { interval = updateInterval; lastTimeStamp = block.timestamp; counter = 0; } function checkUpkeep( bytes calldata /* checkData */ ) external view override returns (bool upkeepNeeded, bytes memory /* performData */) { upkeepNeeded = (block.timestamp - lastTimeStamp) > interval; // We don't use the checkData in this example. The checkData is defined when the Upkeep was registered. } function performUpkeep(bytes calldata /* performData */) external override { //We highly recommend revalidating the upkeep in the performUpkeep function if ((block.timestamp - lastTimeStamp) > interval) { lastTimeStamp = block.timestamp; counter = counter + 1; } // We don't use the performData in this example. The performData is generated by the Automation Node's call to your checkUpkeep function } } ``` 可以看到這個合約的`checkUpkeep`,是去確定現在的timestamp距離上次儲存的timestamp是不是已經超過我們所設定的interval了,並把比較的結果return出來 如果`checkUpkeep`return true,那麼就執行`performUpkeep`,這個範例合約`performUpkeep`做的事情是將`lastTimeStamp`設為現在的timestamp,並將`counter`加一 也就是說如果`interval`設定為60的話,那整個功能就是**每經過60秒`counter`就會加一** 這邊如果要編寫更為複雜的功能的話,建議閱讀官方文檔[Creating Compatible Contracts ](https://docs.chain.link/chainlink-automation/compatible-contracts/) #### 接下來是去創建一個Custom Logic的Upkeep,整體流程跟Time-based相似 1. 進入Register new Upkeep後,選擇Custom Logic 2. 輸入你想要觸發的contract,點選Next,這邊會去驗證你的contract有無兼容 ![](https://i.imgur.com/vk8dK4u.png) 3. 輸入Upkeep的細節設定,這邊可以設定起始資金、GasLimit、Upkeep name等等 4. 點選**Register Upkeep**按鈕 5. 完成 ![](https://i.imgur.com/7HfWKzJ.png) 我部署的是上面官方提供的範例合約,我將`interval`設定為60,所以這邊可以看到他60秒發送一個`Perform Upkeep`的transaction ## 備註 這篇文章僅提供初步的認識跟練習,若要編寫正式的合約建議還是閱讀官方文檔,若上述製作有任何問題可以詢問我,有其他寫錯的地方也歡迎指正 ## Reference [Chainlink Docs](https://docs.chain.link/chainlink-automation/introduction/) [【DAY15】 - Dynamic NFT & Oracle (I)](https://ithelp.ithome.com.tw/m/articles/10299344)