2021-04-08 03:15 | 出处: FLOW福洛链
随着非同质化代币(NFT)市场 达到高潮,回顾一下相对较早的 NFT 并回想起 CryptoKitties 面临的挑战是很有趣的。 这是由 Dapper Labs 团队构建的第一个大规模使用的案例 , 也让以太坊第一次承受了流量压力。
从那时起,NFT 迅速兴起,诸如 Rarible,OpenSea,Foundation 和 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
sh -ci “¥(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)"
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 接口表示,无论我们定义为谁有权访问该资源,都将能够调用以下方法:
depositgetIDsidExistsgetMetadata接下来,我们需要定义我们的令牌收集接口。可以将其视为容纳用户所有 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: 0 x01
现在,我们准备将合约部署到本地运行的模拟器。在命令行中,运行以下命令:flow project start-emulator
现在,在仿真器运行且 flow.json 文件配置正确的情况下,我们可以部署合约。只需运行以下命令:flow project deploy
如果一切顺利,您应该会看到类似以下的输出:
为帐户部署 1 个合约:仿真器帐户Deploying 1 contracts for accounts: emulator-accountPinataPartyContract -> 0 xf8d6e0586b0a20c7
现在,我们在 Flow 仿真器上有一个合约,但是我们想要铸造一个令牌。让我们来结束这篇博客文章。
在本教程的第二篇文章中,我们将致力于通过应用程序和用户界面使铸造过程更加用户友好。为了说明问题,并展示元数据将如何与 Flow 上的 NFT 一起使用,我们将使用 Cadence 脚本和命令行。
让我们在 pinata-party 项目的根目录下创建一个新目录,并将其称为 transactions。创建该文件夹后,在其中创建一个名为的新文件 MintPinataParty.cdc。
为了编写交易,我们需要在提供给 NFT 的元数据中引用一个文件。为此,我们将通过 Pinata 将文件上传到 IPFS。在本教程中,由于我们的 NFT 专注于派对上被砸的 piñata 的可交易的视频,因此我们将上传一个孩子在生日聚会上击中 piñata 的视频。您可以上传任何想要的视频文件。您可以真正上载任何资产文件并将其与 NFT 关联,但是本教程系列的第二篇文章将期待视频内容。准备好要播放的视频文件后,请在此处上传。 上传文件后,系统会为您提供 IPFS 哈希(通常称为内容标识符或 CID)。复制此哈希,因为我们将在铸造过程中使用它。
现在,在 MintPinataParty.cdc 文件内添加以下内容:
import PinataPartyContract from 0 xf8d6e0586b0a20c7 transaction { 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, me