如何在Flow和ipfs上創建像NBA TOP SHOT那樣的NFT
FlowTimes福洛时代
2021-03-16 12:28
本文约9781字,阅读全文需要约39分钟
我們將構建一個非常基本的例子,然後在IPFS 上備份NFT 的元數據。

隨著非同質化代幣(NFT)市場達到高潮,回顧一下相對較早的NFT 並回想起CryptoKitties面臨的挑戰是很有趣的。這是由Dapper Labs

RaribleOpenSeaFoundationSorare之類的平台開始興起。這些平台每月有數百萬美元的資金流動, 但大多數情況還是在以太坊區塊鏈上發生的。然而,Dapper Labs 的團隊有了開發CryptoKitties 的經驗之後,便著手建立一個新的公鏈,該公鏈將是通用的,但也非常適合NFT 的開發。這樣做的目的是解決以太坊上的NFT 所遇到的許多問題,同時為該領域的開發者和收藏家提供更好的體驗。他們的新區塊鏈Flow,已經證明自己有能力贏得一些知名品牌的合作。例如NBA,UFC,甚至Dr. Seuss 都也在flow 上。

我們最近寫過有關於在IPFS 上創建具有內置資產支持的NFT的文章,並且我們討論了NFT 領域中的責任問題以及我們認為IPFS 可以如何提供幫助。現在是時候討論如何在IPFS 支持的flow 上創建nft 了。 Flow 區塊鏈的主要早期應用之一是NBA Top Shot。我們將構建一個非常基本的例子,然後在IPFS 上備份NFT 的元數據。

由於我們喜歡piñatas,而不是NBA 精彩片段視頻,因此我們將創建一個可交易的piñatas 在派對上被打碎的視頻。

這是一個三部分的教程:
1. 創建合約並鑄造代幣
2. 創建一個app 來查看通過此合約創建的NFT
3. 創建一個市場以將NFT 轉讓給其他人,同時也轉讓這個NFT 在IPFS 上的基礎資產

配置

配置

我們需要安裝Flow CLI。 Flow 的文檔中有一些很好的安裝說明,但我將在此處複製它們:

蘋果系統
brew install flow-cli

Linux

sh -ci “$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)"

Windows

iex “& { $(irm ‘https://storage.googleapis.com/flow-cli/install.ps1') }”

我們將在IPFS 上存儲資產文件。為了簡化操作,我們可以使用Pinata。您可以在此處註冊一個免費帳戶,並在此處獲取API 密鑰。在本教程的第二篇文章中,我們將使用API,但在這篇文章中,我們將使用Pinata 網站。

我們還需要安裝NodeJS 和文本編輯器,以幫助突出顯示Flow 智能合約代碼(以Cadence語言編寫)的語法。您可以在此處安裝Node。 Visual Studio Code 具有支持Cadence 的擴展

讓我們創建一個目錄來開始我們的項目。

mkdir pinata-party

進到該目錄並初始化一個新的flow 項目:

cd pinata-party
flow project init

現在,在您喜歡的代碼編輯器中打開項目(同樣,如果您使用Visual Studio Code,請安裝Cadence 擴展),然後開始開發。

您會看到一個flow.json 文件,我們將很快使用它。首先,創建一個名為cadence 的文件夾。在該文件夾中,添加另一個名為contracts 的文件夾。最後,在contracts 文件夾中創建一個叫PinataPartyContract.cdc 的文件。

在繼續前進之前,重要的一點是要指出,從現在開始,我們對Flow 區塊鏈所做的一切都將在模擬器上完成。但是,將項目部署到testnet 或mainnet 就像更新flow.json 文件中的配置設置一樣簡單。現在讓我們為仿真器環境設置該文件,然後就可以開始編寫合約了。

更新flow.json 中的contracts, 如下所示:

"contracts": {
    "PinataPartyContract": "./cadence/contracts/PinataPartyContract.cdc"
}

然後, 更新flow.json 中的deployments, 如下所示:

"deployments": {
    "emulator": {
         "emulator-account": ["PinataPartyContract"]
    }
}

合約

合約

合約

Flow 提供了有關創建NFT 合約的出色教程。這是一個很好的參考點,但是正如Flow指出的那樣,他們尚未解決NFT 元數據問題。他們想在鏈上存儲元數據。那是個好主意,他們一定會提出一個合乎邏輯的方法。但是,我們現在想用元數據創建一些令牌,並且我們希望與NFT 相關聯的媒體文件。元數據只是一個組成部分。我們還需要指出令牌最終代表的媒體文件。

如果您熟悉以太坊區塊鏈上的NFT,您可能會知道這些令牌返回的許多資產都存儲在傳統數據存儲和雲服務商中。在它們不宕機的情況下這是可以的。過去我們曾寫過關於內容可尋以及在傳統雲平台上存儲與存儲在區塊鏈上的比較。歸結為兩點:

  • 資產應該是可驗證的

  • 存儲的轉移應該很容易

IPFS考慮到了這兩個方面。然後,Pinata 以一種簡單的方式分層,以將內容長期存儲在IPFS 上。這正是我們想要支持NFT 的媒體所需要的,對嗎?我們要確保可以證明所有權(NFT),提供有關NFT (NFT)的數據,並確保我們對基礎資產(IPFS)-(媒體文件或其他)具有控制權,而不是某些複製品的控制權。考慮到所有這些,讓我們編寫一個合約,創建NFT,將元數據與NFT 相關聯,並確保元數據指向IPFS 上存儲的基礎資產。

打開PinataPartyContract.cdc,讓我們開始工作。

pub contract PinataPartyContract {
 pub resource NFT {
   pub let id: UInt64
   init(initID: UInt64) {
     self.id = initID
   }
 }
}

第一步是定義我們的合約。我們將為此添加更多的內容,但是我們首先定義PinataPartyContract 並在其中創建一個resource。資源是存儲在用戶帳戶中的項目,可以通過訪問控制進行訪問。在這種情況下,NFT 資源最終是因為擁有用於表示NFT 的事物而擁有的。 NFT 必須是唯一可識別的。該id 屬性使我們能夠識別令牌。

接下來,我們需要創建一個資源接口,該接口將用於定義哪些功能可供其他人(即不是合約所有者的人)使用:

pub resource interface NFTReceiver {
 pub fun deposit(token: @NFT, metadata: {String : String})
 pub fun getIDs(): [UInt64]
 pub fun idExists(id: UInt64): Bool
 pub fun getMetadata(id: UInt64) : {String : String}
}

將其放在NFT 資源代碼下方。該NFTReceiver 接口表示,無論我們定義為誰有權訪問該資源,都將能夠調用以下方法:

  • deposit

  • getIDs

  • idExists

  • getMetadata

接下來,我們需要定義我們的令牌收集接口。可以將其視為容納用戶所有NFT 的錢包。

pub resource Collection: NFTReceiver {
   pub var ownedNFTs: UInt64: NFT}
   pub var metadataObjs: {UInt64: { String : String }}

   init () {
       self.ownedNFTs <- {}
       self.metadataObjs = {}
   }

   pub fun withdraw(withdrawID: UInt64): @NFT {
       let token <- self.ownedNFTs.remove(key: withdrawID)!

       return <-token    }

   pub fun deposit(token: @NFT, metadata: {String : String}) {
       self.ownedNFTs[token.id] <-! token    }

   pub fun idExists(id: UInt64): Bool {
       return self.ownedNFTs[id] != nil    }

   pub fun getIDs(): [UInt64] {
       return self.ownedNFTs.keys    }

   pub fun updateMetadata(id: UInt64, metadata: {String: String}) {
       self.metadataObjs[id] = metadata    }

   pub fun getMetadata(id: UInt64): {String : String} {
       return self.metadataObjs[id]!
   }

   destroy() {
       destroy self.ownedNFTs    }}

這個資源中有很多事情要做。首先,我們有一個名為ownedNFTs 的變量。這很簡單。它跟踪該合約中用戶擁有的所有NFT。

接下來,我們有一個名為metadataObjs 的變量。這一點有點獨特,因為我們正在擴展Flow NFT 合約功能,以存儲每個NFT 的元數據映射。此變量將令牌ID 映射到其關聯的元數據,這意味著我們需要先設置令牌ID,然後才能進行設置。

然後,我們初始化變量。對於Flow 中資源中定義的變量,這是必需的。

最後,我們擁有NFT 收集資源的所有可用功能。請注意,並非所有這些功能都可以使用。如果您還記得的話,我們在NFTReceiver 資源界面的前面定義了任何人都可以使用的功能。

我確實要指出deposit 功能。正如我們將默認的Flow NFT 合約擴展為包括metadataObjs 映射一樣,我們也在擴展默認deposit 函數以採用的附加參數metadata。我們為什麼在這裡這樣做?我們需要確保只有令牌的鑄造者才能將該元數據添加到令牌中。為了保持私密性,我們將最初添加的元數據限制在鑄造執行中。

我們的合約代碼幾乎完成了。因此,在Collection 資源下方,添加以下內容:

pub fun createEmptyCollection(): @Collection {
   return <- create Collection()}pub resource NFTMinter {
   pub var idCount: UInt64

   init() {
       self.idCount = 1
   }

   pub fun mintNFT(): @NFT {
       var newNFT <- create NFT(initID: self.idCount)

       self.idCount = self.idCount + 1 as UInt64        return <-newNFT    }}

首先,我們有一個函數,該函數在調用時會創建一個空的NFT 集合。這樣,首次與我們的合約進行交互的用戶將具有一個創建到Collection 我們定義的資源的存儲位置。

之後,我們再創建一個資源。這很重要,因為沒有它,我們將無法鑄造代幣。該NFTMinter 資源包括idCount 其遞增,以確保我們從來沒有對我們的NFT 重複的ID。它還具有實際創建我們的NFT 的功能。

在NFTMinter 資源下方,添加主合約初始化程序:

init() {
      self.account.save(<-self.createEmptyCollection(), to: /storage/NFTCollection)
       self.account.link<&{NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection)
      self.account.save(<-create NFTMinter(), to: /storage/NFTMinter)
      }

僅在部署合約時才調用此初始化函數。它做三件事:
1. 為集合的部署者創建一個空的集合,以便合約的所有者可以創建該合約的NFT 並擁有該NFT。
2.Collection 參考NFTReceiver 我們在開始時創建的界面,該資源在公共位置發布。這就是我們告訴合約的方式,NFTReceiver 任何人都可以調用上定義的功能。
3. 該NFTMinter 資源被保存在賬戶儲存合約的創造者。這意味著只有合約的創建者才能鑄造代幣。

完整的合約可以在這裡找到。

現在我們準備好了合約,讓我們部署它,對吧?好吧,我們可能應該在Flow Playground上對其進行測試。轉到那裡,然後單擊左側邊欄中的第一個帳戶。用我們的合約代碼替換示例合約中的所有代碼,然後單擊“部署”。如果一切順利,您應該在屏幕底部的日誌窗口中看到這樣的日誌:

16:48:55 Deployment Deployed Contract To: 0x01

現在,我們準備將合約部署到本地運行的模擬器。在命令行中,運行以下命令:
flow project start\-emulator

現在,在仿真器運行且flow.json 文件配置正確的情況下,我們可以部署合約。只需運行以下命令:
flow project deploy

如果一切順利,您應該會看到類似以下的輸出:
鑄造NFT
Deploying 1 contracts for accounts: emulator-account
PinataPartyContract -> 0xf8d6e0586b0a20c7
鑄造NFT

鑄造NFT

在本教程的第二篇文章中,我們將致力於通過應用程序和用戶界面使鑄造過程更加用戶友好。為了說明問題,並展示元數據將如何與Flow 上的NFT 一起使用,我們將使用Cadence 腳本和命令行。
讓我們在pinata-party 項目的根目錄下創建一個新目錄,並將其稱為transactions。創建該文件夾後,在其中創建一個名為的新文件MintPinataParty.cdc。

為了編寫交易,我們需要在提供給NFT 的元數據中引用一個文件。為此,我們將通過Pinata 將文件上傳到IPFS。在本教程中,由於我們的NFT 專注於派對上被砸的piñata 的可交易的視頻,因此我們將上傳一個孩子在生日聚會上擊中piñata 的視頻。您可以上傳任何想要的視頻文件。您可以真正上載任何資產文件並將其與NFT 關聯,但是本教程系列的第二篇文章將期待視頻內容。準備好要播放的視頻文件後,請在此處上傳。上傳文件後,系統會為您提供IPFS 哈希(通常稱為內容標識符或CID)。複製此哈希,因為我們將在鑄造過程中使用它。

現在,在MintPinataParty.cdc 文件內添加以下內容:

import PinataPartyContract from 0xf8d6e0586b0a20c7transaction {
 let receiverRef: &{PinataPartyContract.NFTReceiver}
 let minterRef: &PinataPartyContract.NFTMinter

 prepare(acct: AuthAccount) {
     self.receiverRef = acct.getCapability<&{PinataPartyContract.NFTReceiver}>(/public/NFTReceiver)
         .borrow()
         ?? panic("Could not borrow receiver reference")

     self.minterRef = acct.borrow<&PinataPartyContract.NFTMinter>(from: /storage/NFTMinter)
         ?? panic("could not borrow minter reference")
 }

 execute {
     let metadata : {String : String} = {
         "name": "The Big Swing",
         "swing_velocity": "29",
         "swing_angle": "45",
         "rating": "5",
         "uri": "ipfs://QmRZdc3mAMXpv6Akz9Ekp1y4vDSjazTx2dCQRkxVy1yUj6"
     }
     let newNFT <- self.minterRef.mintNFT()

     self.receiverRef.deposit(token: <-newNFT, metadata: metadata)

     log("NFT Minted and deposited to Account 2's Collection")
 }}

這是一個非常簡單的事務,這在很大程度上要感謝Flow 為使事情變得容易而進行的工作,但讓我們逐步進行一下。首先,您會在頂部注意到import 語句。如果您還記得的話,當我們部署合約時,我們會收到一個帳戶。這就是我們需要參考的內容。因此,請替換0xf8d6e0586b0a20c7為您部署中的帳戶地址。

接下來,我們定義交易。這裡發生的一切都與我們計劃執行的交易有關。

我們在交易中要做的第一件事是定義兩個參考變量receiverRef 和minterRef。在這種情況下,我們既是NFT 的接收者又是NFT 的鑄造者。這兩個變量引用了我們在合約中創建的資源。如果執行事務的人無權訪問該資源,則事務將失敗。

接下來,我們有一個prepare 功能。此功能獲取嘗試執行交易的人員的帳戶信息並進行一些驗證。我們嘗試“借用”我們定義的NFTMinter 和兩種資源上的可用功能NFTReceiver。如果執行交易的人沒有訪問這些資源的權限,那麼事情將會失敗。

最後,我們有我們的execute 功能。此功能是我們為NFT 建立元數據,創建NFT,然後關聯元數據,然後再將NFT 存入我們的帳戶的地方。如果您注意到的話,我創建了一個元數據變量。在該變量中,我添加了一些有關令牌的信息。由於我們的代幣表示在聚會上砸了一個披薩的事件,並且由於我們試圖複製您在NBA Top Shot 中看到的大部分內容,因此我在元數據中定義了一些統計信息。孩子揮動棍子打皮納塔的速度,揮桿角度和等級。我只是在玩這些統計數據。但是,您將以類似的方式輸入對您的令牌有意義的任何信息。

您會注意到,我也在uri 元數據中定義了一個屬性。這將指向承載與NFT 關聯的資產文件的IPFS 哈希。在這種情況下,這是被擊中的Piñata 的實際視頻。您可以使用之前上傳文件後收到的哈希值替換哈希值。

我們將hash 添加上了ipfs:// 前綴。這是IPFS 上文件的正確參考,可以與IPFS 的桌面客戶端和瀏覽器擴展一起使用。 brave 也為其提供了支持,我們也可以將其直接粘貼到Brave 瀏覽器中

我們調用mintNFT 創建令牌的函數。然後,我們必須調用該deposit 函數以將其放入我們的帳戶。這也是我們傳遞元數據的地方。請記住,我們在deposit 函數中定義了一個變量關聯,該關聯將元數據添加到關聯的令牌ID。

現在,我們幾乎準備發送交易並創建NFT。但是首先,我們需要準備我們的帳戶。從項目的根文件夾中的命令行,讓我們創建一個用於簽名的新私鑰。運行以下命令:

flow keys generate

這將為您提供一個公鑰和一個私鑰。始終保護您的私鑰

我們將需要私鑰來簽署交易,因此我們可以將其粘貼到我們的flow.json 文件中。我們還需要指定簽名算法。這是文件中的accounts 對象flow.json 現在應如下所示:

“ accounts”:{
 “ emulator-account”:{
“ address”:“您的帳戶地址”,
“ privateKey”:“您的私鑰”,
“ chain”:“流仿真器”,
    “ sigAlgorithm”:“ ECDSA_P256”,
    “ hashAlgorithm”:“ SHA3_256”
 }
},

如果您打算將此項目中的任何一個存儲在github 或任何遠程git 存儲庫上,請確保不包括私鑰。您可以在.gitignore 中添加flow.json。即使我們僅使用本地仿真器,這還是保護您的密鑰的一種很好的做法。

現在我們已經更新了,我們可以發送交易了。這樣做就像運行以下命令一樣簡單:
flow transactions send --code ./transactions/MintPinataParty.cdc --signer emulator-account

我們從中引用了我們編寫的交易文件和簽名人帳戶flow.json。如果一切順利,您應該會看到類似以下的輸出:

Getting information for account with address 0xf8d6e0586b0a20c7 ...
Submitting transaction with ID
4a79102747a450f65b6aab06a77161af196c3f7151b2400b3b3d09ade3b69823 ...
Successfully submitted transaction with ID
4a79102747a450f65b6aab06a77161af196c3f7151b2400b3b3d09ade3b69823

現在,我們要做的最後一件事是驗證令牌是否在我們的帳戶中並獲取元數據。這樣做,我們將編寫一個非常簡單的腳本並從命令行調用它。

在項目的根目錄中,創建一個名為scripts 的新文件夾。在其中創建一個名為的文件CheckTokenMetadata.cdc。在該文件中,添加以下內容:

import PinataPartyContract from 0xf8d6e0586b0a20c7pub fun main() : {String : String} {
   let nftOwner = getAccount(0xf8d6e0586b0a20c7)
   // log("NFT Owner")
   let capability = nftOwner.getCapability<&{PinataPartyContract.NFTReceiver}>(/public/NFTReceiver)

   let receiverRef = capability.borrow()
       ?? panic("Could not borrow the receiver reference")

   return receiverRef.getMetadata(id: 1)}

可以以類似於以太坊智能合約上的只讀方法的方式來考慮該腳本。他們是免費的,只需從合約中返回數據即可。

在腳本中,我們從部署的地址導入合約。然後,我們定義一個main 函數(這是運行腳本所需的函數名稱)。在此函數內部,我們定義了三個變量:

  • nftOwner:這只是擁有NFT 的帳戶。我們從也部署了合約的帳戶中鑄造了NFT,因此在我們的示例中,這兩個地址是相同的。取決於將來的合約設計,這可能並不總是正確的。

  • capability:我們需要從已部署的合約中“借用”。請記住,這些功能是受訪問控制的,因此,如果某功能對於嘗試借用它的地址不可用,則腳本將失敗。我們正在從NFTReceiver 資源中藉出。

  • receiverRef:該變量只是利用我們的能力,並告訴腳本從已部署的合約中藉用。

現在,我們可以調用函數(可用的函數)了。在這種情況下,我們要確保所討論的地址實際上已收到我們鑄造的NFT,然後我們要查看與令牌關聯的元數據。

讓我們運行我們的腳本,看看我們得到了什麼。在命令行上運行以下命令:
flow scripts execute --code ./scripts/CheckTokenMetadata.cdc

對於元數據輸出,您應該看到類似以下的輸出:
{"name": "The Big Swing", "swing_velocity": "29", "swing_angle": "45", "rating": "5", "uri": "ipfs://QmRZdc3mAMXpv6Akz9Ekp1y4vDSjazTx2dCQRkxVy1yUj6"}

恭喜你!您已成功創建Flow 智能合約,鑄造了令牌以及與該令牌相關的元數據,並將該令牌的基礎數字資產存儲在IPFS 上。對於教程的第一部分來說還不錯。

接下來,我們有一個有關構建前端React 應用程序的教程,通過獲取元數據並解析該元數據,您可以顯示NFT

FlowTimes福洛时代
作者文库