如何在Flow和ipfs上创建像NBA TOP SHOT那样的NFT

本文约5615字,阅读全文需要约8分钟
我们将构建一个非常基本的例子,然后在 IPFS 上备份 NFT 的元数据。

随着非同质化代币(NFT)市场 达到高潮,回顾一下相对较早的 NFT 并回想起 CryptoKitties 面临的挑战是很有趣的。 这是由 Dapper Labs 团队构建的第一个大规模使用的案例 , 也让以太坊第一次承受了流量压力。

从那时起,NFT 迅速兴起,诸如 RaribleOpenSeaFoundation 和 Sorare 之类的平台开始兴起。这些平台每月有数百万美元的资金流动 , 但大多数情况还是在以太坊区块链上发生的。然而,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 CLI 使用仿真器来部署我们的合约,它还引用了帐户(在仿真器上)和我们即将写的合约。

现在让我们继续下去。

合约

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

如果一切顺利,您应该会看到类似以下的输出:
为帐户部署 1 个合约:仿真器帐户
Deploying 1 contracts for accounts: emulator-account
PinataPartyContract -> 0xf8d6e0586b0a20c7
现在,我们在 Flow 仿真器上有一个合约,但是我们想要铸造一个令牌。让我们来结束这篇博客文章。

铸造 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 2s 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

本文来自投稿,不代表Odaily立场。如若转载请注明出处。

ODAILY提醒,请广大读者树立正确的货币观念和投资理念,理性看待区块链,切实提高风险意识;对发现的违法犯罪线索,可积极向有关部门举报反映。

推荐阅读
星球精选