paint-brush
如何使用 Flow 和 Cadence 构建数字收藏品门户(第 1 部分)经过@johnjvester
303 讀數
303 讀數

如何使用 Flow 和 Cadence 构建数字收藏品门户(第 1 部分)

经过 John Vester20m2024/02/13
Read on Terminal Reader

太長; 讀書

通过建立一个专注于收集数字收藏品的新网站,更多地了解 Flow 区块链和 Cadence 智能合约语言。
featured image - 如何使用 Flow 和 Cadence 构建数字收藏品门户(第 1 部分)
John Vester HackerNoon profile picture

在本教程中,我们将学习如何构建一个网站,用于在区块链 Flow 上收集数字收藏品(或 NFT)。我们将使用智能合约语言 Cadence 和 React 来实现这一切。我们还将了解 Flow、它的优点以及我们可以使用的有趣工具。


读完本文后,您将拥有在 Flow 区块链上创建自己的去中心化应用程序所需的工具和知识。


让我们开始吧!


我们正在建设什么?

我们正在构建一个数字收藏品应用程序。每个收藏品都是不可替代的代币(NFT)。 (如果你是新手,不了解 NFT,那么请看这里。)我们的应用程序将允许您收集 NFT,并且每件物品都将是独一无二的。


为了使这一切顺利进行,我们将使用 Flow 的 NonFungibleToken 标准,这是一组帮助我们管理这些特殊数字项目的规则(类似于以太坊中的 ERC-721)。

先决条件

开始之前,请务必在您的系统上安装 Flow CLI。如果您还没有这样做,请按照这些安装说明进行操作。

配置

如果您准备好启动项目,请首先输入命令流设置。


该命令在幕后发挥了一些作用,为您的项目奠定了基础。它创建一个文件夹系统并设置一个名为 flow.json 的文件来配置您的项目,确保一切都井井有条并准备就绪!


项目结构

该项目将包含cadence文件夹和flow.json文件。 (flow.json 文件是项目的配置文件,自动维护。)

Cadence 文件夹包含以下内容:

  • /contracts:包含所有 Cadence 合约。
  • /scripts:保存所有 Cadence 脚本。
  • /transactions:存储所有 Cadence 事务。


请按照以下步骤使用 Flow NFT 标准。

第 1 步:创建一个文件。

首先,转到flow-collectibles-portal文件夹,找到cadence文件夹。然后,打开contracts文件夹。创建一个新文件,并将其命名为NonFungibleToken.cdc

第 2 步:复制并粘贴。

现在,打开名为NonFungibleToken的链接,其中包含 NFT 标准。复制该文件中的所有内容,并将其粘贴到您刚刚创建的新文件(“NonFungibleToken.cdc”)中。


就是这样!您已经成功地为您的项目设置了标准。

现在,让我们编写一些代码!


然而,在我们深入编码之前,开发人员建立如何构建代码的心理模型非常重要。


在顶层,我们的代码库由三个主要组件组成:

  1. NFT:每个收藏品都表示为 NFT。


  2. 集合:集合是指特定用户拥有的一组 NFT。


  3. 全局函数和变量:这些是在智能合约的全局级别定义的函数和变量,不与任何特定资源关联。

智能合约结构

智能合约基本结构

cadence/contracts中创建一个名为Collectibles.cdc的新文件。这是我们将编写代码的地方。


合约结构

import NonFungibleToken from "./NonFungibleToken.cdc" pub contract Collectibles: NonFungibleToken{ pub var totalSupply: UInt64 // other code will come here init(){ self.totalSupply = 0 } }


让我们逐行分解代码:


  1. 首先,我们需要通过包含所谓的“NonFungibleToken”来标准化我们正在构建的 NFT。这是由 Flow 构建的 NFT 标准,定义了每个 NFT 智能合约必须包含的以下功能集。


  2. 导入后,让我们创建合同。为此,我们使用pub contract [contract name] 。每次创建新合约时都使用相同的语法。您可以用您想要的合同名称填写contract name 。在我们的例子中,我们将其称为Collectibles


  3. 接下来,我们要确保我们的合约遵循 NonFungibleToken 的一组特定功能和规则。为此,我们在“:”的帮助下添加一个 NonFungibleToken 接口。
    像这样( `pub contract Collectibles: NonFungibleToken{}`


  4. 每个合约都必须具有init()函数。它在合约最初部署时被调用。这类似于 Solidity 所说的构造函数。


  5. 现在,我们创建一个名为totalSupply数据类型为UInt64全局变量。该变量将跟踪您的收藏品总数。


  6. 现在,用值0初始化totalSupply


就是这样!我们为Collectibles合同奠定了基础。现在,我们可以开始添加更多特性和功能,使其更加令人兴奋。


在继续之前,请查看代码片段以了解我们如何在 Cadence 中定义变量:


资源NFT

将以下代码添加到您的智能合约中:


 import NonFungibleToken from "./NonFungibleToken.cdc" pub contract Collectibles: NonFungibleToken{ // above code… pub resource NFT: NonFungibleToken.INFT{ pub let id: UInt64 pub var name: String pub var image: String init(_id:UInt64, _name:String, _image:String){ self.id = _id self.name = _name self.image = _image } } // init()... }


正如您之前所看到的,该合约实现了 NFT 标准接口,以pub contract Collectibles: NonFungibleToken为代表。同样,资源也可以实现各种资源接口。


因此,让我们将NonFungibleToken.INFT接口添加到 NFT 资源中,该接口要求资源中存在一个名为 id 的公共属性。

以下是我们将在 NFT 资源中使用的变量:


  • id:维护NFT的ID
  • name: NFT 的名称。
  • image: NFT 的图片 URL。


定义变量后,请务必在init()函数中初始化该变量。


让我们继续创建另一个名为Collection Resource的资源。

馆藏资源

首先,您需要了解Collection Resources工作原理。


如果您需要在笔记本电脑上存储音乐文件和几张照片,您会怎么做?


通常,您会导航到本地驱动器(假设您的 D 驱动器)并创建music文件夹和photos文件夹。然后,您可以将音乐和照片文件复制并粘贴到目标文件夹中。

同样,这就是 Flow 上的数字收藏品的工作原理。


想象一下,您的笔记本电脑是Flow Blockchain Account ,您的 D 驱动器是Account Storage ,文件夹是Collection


因此,当与任何项目交互购买 NFT 时,该项目会在您的account storage中创建其collection ,类似于在 D 驱动器上创建文件夹。当您与 10 个不同的 NFT 项目交互时,您的帐户中最终会出现 10 个不同的集合。


这就像拥有一个个人空间来存储和整理您独特的数字宝藏!


 import NonFungibleToken from "./NonFungibleToken.cdc" pub contract Collectibles: NonFungibleToken{ //Above code NFT Resource… // Collection Resource pub resource Collection{ } // Below code… }


每个collection都有一个ownedNFTs变量来保存NFT Resources


 pub resource Collection { pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT} init(){ self.ownedNFTs <- {} } }


资源接口

Flow 中的resource接口与其他编程语言中的接口类似。它位于资源之上,并确保实现它的资源具有接口定义的所需功能。


它还可用于限制对整个资源的访问,并且在访问修饰符方面比资源本身更具限制性。


NonFungibleToken标准中,有几个资源接口,例如INFTProviderReceiverCollectionPublic


每个接口都有特定的功能和字段,需要由使用它们的资源来实现。


在此合约中,我们将使用NonFungibleToken: ProviderReceiverCollectionPublic 。这些接口定义了depositwithdrawborrowNFTgetIDs功能。我们将详细解释其中每一个。


我们还将添加一些从这些函数发出的事件,并声明一些我们将在本教程中进一步使用的变量。


 pub contract Collectibles:NonFungibleToken{ // rest of the code… pub event ContractInitialized() pub event Withdraw(id: UInt64, from: Address?) pub event Deposit(id: UInt64, to: Address?) pub let CollectionStoragePath: StoragePath pub let CollectionPublicPath: PublicPath pub resource interface CollectionPublic{ pub fun deposit(token: @NonFungibleToken.NFT) pub fun getIDs(): [UInt64] pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT } pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic{ pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT} init(){ self.ownedNFTs <- {} } } }


提取


现在,让我们创建该接口所需的withdraw()函数。


 pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic{ // other code pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT { let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT") emit Withdraw(id: token.id, from: self.owner?.address) return <- token } init()... }


借助该功能,您可以将 NFT 资源移出集合。如果它:


  • 失败:恐慌并抛出错误。


  • 成功:它发出撤回事件并将资源返回给调用者。


然后,调用者可以使用该资源并将其保存在其帐户存储中。


订金

现在,是时候使用NonFungibleToken.Receiver所需的deposit()函数了。


 pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic{ // other code pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT { let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT") emit Withdraw(id: token.id, from: self.owner?.address) return <- token } pub fun deposit(token: @NonFungibleToken.NFT) { let id = token.id let oldToken <- self.ownedNFTs[id] <-token destroy oldToken emit Deposit(id: id, to: self.owner?.address) } init()... }


借用并获取ID


现在,让我们关注NonFungibleToken.CollectionPublic: borrowNFT()getID()


 pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic{ // other code pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT { let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT") emit Withdraw(id: token.id, from: self.owner?.address) return <- token } pub fun deposit(token: @NonFungibleToken.NFT) { let id = token.id let oldToken <- self.ownedNFTs[id] <-token destroy oldToken emit Deposit(id: id, to: self.owner?.address) } pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT { if self.ownedNFTs[id] != nil { return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)! } panic("NFT not found in collection.") } pub fun getIDs(): [UInt64]{ return self.ownedNFTs.keys } init()... }


析构函数

对于集合资源,我们最不需要的就是析构函数。


 destroy (){ destroy self.ownedNFTs }


由于Collection资源包含其他资源(NFT资源),因此我们需要指定一个析构函数。当对象被销毁时,析构函数就会运行。这确保了资源在其父资源被破坏时不会“无家可归”。我们不需要 NFT 资源的析构函数,因为它不包含任何其他资源。


我们看一下完整的集合资源源码:


 import NonFungibleToken from "./NonFungibleToken.cdc" pub contract Collectibles: NonFungibleToken{ pub var totalSupply: UInt64 pub resource NFT: NonFungibleToken.INFT{ pub let id: UInt64 pub var name: String pub var image: String init(_id:UInt64, _name:String, _image:String){ self.id = _id self.name = _name self.image = _image } } pub resource interface CollectionPublic{ pub fun deposit(token: @NonFungibleToken.NFT) pub fun getIDs(): [UInt64] pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT } pub event ContractInitialized() pub event Withdraw(id: UInt64, from: Address?) pub event Deposit(id: UInt64, to: Address?) pub let CollectionStoragePath: StoragePath pub let CollectionPublicPath: PublicPath pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic{ pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT} init(){ self.ownedNFTs <- {} } destroy (){ destroy self.ownedNFTs } pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT { let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT") emit Withdraw(id: token.id, from: self.owner?.address) return <- token } pub fun deposit(token: @NonFungibleToken.NFT) { let id = token.id let oldToken <- self.ownedNFTs[id] <-token destroy oldToken emit Deposit(id: id, to: self.owner?.address) } pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT { if self.ownedNFTs[id] != nil { return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)! } panic("NFT not found in collection.") } pub fun getIDs(): [UInt64]{ return self.ownedNFTs.keys } } init(){ self.CollectionPublicPath = /public/NFTCollection self.CollectionStoragePath = /storage/NFTCollection self.totalSupply = 0 emit ContractInitialized() } }


现在,我们已经完成了所有资源。接下来,我们将看看全局函数。

全局功能

全局函数是在智能合约的全局级别上定义的函数,这意味着它们不属于任何资源。这些可供公众访问和调用,并向公众公开智能合约的核心功能。


  1. createEmptyCollection :此函数将一个空的Collectibles.Collection初始化到调用者帐户存储中。


  2. checkCollection :这个方便的功能可以帮助您发现您的帐户是否已经有collection资源。


  3. mintNFT :这个功能非常酷,因为它允许任何人创建 NFT。


 // pub resource Collection… pub fun createEmptyCollection(): @Collection{ return <- create Collection() } pub fun checkCollection(_addr: Address): Bool{ return getAccount(_addr) .capabilities.get<&{Collectibles.CollectionPublic}> (Collectibles.CollectionPublicPath)! .check() } pub fun mintNFT(name:String, image:String): @NFT{ Collectibles.totalSupply = Collectibles.totalSupply + 1 let nftId = Collectibles.totalSupply var newNFT <- create NFT(_id:nftId, _name:name, _image:image) return <- newNFT } init()...


总结智能合约

现在,一切就绪后,我们终于完成了智能合约的编写。在这里查看最终代码。


现在,让我们看看用户如何与部署在 Flow 区块链上的智能合约进行交互。


与 Flow 区块链交互有两个步骤:


  1. 通过运行事务来改变状态。
  2. 通过运行脚本查询区块链。

通过运行事务改变状态

交易是经过加密签名的数据,其中包含一组与智能合约交互以更新 Flow 状态的指令。简单来说,这就像一个改变区块链上数据的函数调用。交易通常会涉及一些成本,该成本可能会根据您所在的区块链而有所不同。


事务包括多个可选阶段: preparepreexecute阶段和post阶段。


您可以在有关事务的 Cadence 参考文档中阅读更多相关内容。每个阶段都有一个目的;最重要的两个阶段是prepareexecute


Prepare Phase :此阶段用于访问签名者帐户内的数据和信息(AuthAccount 类型允许)。


Execute Phase :此阶段用于执行操作。


现在,让我们为我们的项目创建一个交易。


按照以下步骤在项目文件夹中创建事务。

第 1 步:创建一个文件。

首先,转到项目文件夹并打开cadence文件夹。在其中,打开transaction文件夹,并创建一个名为Create_Collection.cdcmint_nft.cdc的新文件

步骤2:添加创建集合交易代码。

 import Collectibles from "../contracts/Collectibles.cdc" transaction { prepare(signer: AuthAccount) { if signer.borrow<&Collectibles.Collection>(from: Collectibles.CollectionStoragePath) == nil { let collection <- Collectibles.createEmptyCollection() signer.save(<-collection, to: Collectibles.CollectionStoragePath) let cap = signer.capabilities.storage.issue<&{Collectibles.CollectionPublic}>(Collectibles.CollectionStoragePath) signer.capabilities.publish( cap, at: Collectibles.CollectionPublicPath) } } }


让我们逐行分解这段代码:


  1. 该交易与收藏品智能合约交互。然后,它通过从指定存储路径Collectibles.CollectionStoragePath借用对 Collection 资源的引用来检查发送者(签名者)的帐户中是否存储有 Collection 资源。如果引用为零,则意味着签名者还没有集合。


  2. 如果签名者没有集合,则会通过调用createEmptyCollection()函数创建一个空集合。


  3. 创建空集合后,将其放入签名者帐户中指定的存储路径Collectibles.CollectionStoragePath下。


这将使用link()在签名者的帐户和新创建的集合之间建立链接。

步骤3:添加Mint NFT交易代码。

 import NonFungibleToken from "../contracts/NonFungibleToken.cdc" import Collectibles from "../contracts/Collectibles.cdc" transaction(name:String, image:String){ let receiverCollectionRef: &{NonFungibleToken.CollectionPublic} prepare(signer:AuthAccount){ self.receiverCollectionRef = signer.borrow<&Collectibles.Collection>(from: Collectibles.CollectionStoragePath) ?? panic("could not borrow Collection reference") } execute{ let nft <- Collectibles.mintNFT(name:name, image:image) self.receiverCollectionRef.deposit(token: <-nft) } }


让我们逐行分解这段代码:

  1. 我们首先导入NonFungibleTokenCollectibles contract


  2. transaction(name: String, image: String)此行定义了一个新事务。它有两个参数,名称和图像,都是字符串类型。这些参数用于传递正在铸造的 NFT 的名称和图像。


  3. let receiverCollectionRef: &{NonFungibleToken.CollectionPublic}此行声明一个新变量receiverCollectionRef.它是对NonFungibleToken.CollectionPublic类型的 NFT 公共集合的引用。该参考将用于与我们将存放新铸造的 NFT 的集合进行交互。


  4. prepare(signer: AuthAccount)此行启动准备块,该块在事务之前执行。它需要一个类型为AuthAccount的参数签名者。 AuthAccount代表交易签名者的帐户。


  5. 它从准备块内的签名者存储中借用了对Collectibles.Collection的引用。它使用借用函数来访问集合的引用并将其存储在receiverCollectionRef变量中。


    如果找不到引用(例如,如果签名者的存储中不存在集合),则会抛出错误消息“无法借用集合引用”。


  6. execute块包含事务的主要执行逻辑。该块内的代码将在prepare块成功完成后执行。


  7. nft <- Collectibles.mintNFT(_name: name, image: image)execute块内,此行使用提供的名称和图像参数从Collectibles合约中调用mintNFT函数。该函数预计将创建一个具有给定名称和图像的新 NFT。 <-符号表示 NFT 正在作为可移动的对象(资源)被接收。


  8. self.receiverCollectionRef.deposit(token: <-nft)此行将新铸造的 NFT 存入指定集合中。它使用receiverCollectionRef上的存款函数将NFT的所有权从交易的执行帐户转移到集合。这里的<-符号也表示NFT在deposit过程中作为资源被转移。

通过运行脚本查询区块链

我们使用脚本来查看或读取区块链中的数据。脚本是免费的,不需要签名。

按照以下步骤在项目文件夹中创建脚本。

第 1 步:创建一个文件。

首先,转到项目文件夹并打开cadence文件夹。在其中打开script文件夹,并创建一个名为view_nft.cdc的新文件。

第二步:查看NFT脚本

import NonFungibleToken from "../contracts/NonFungibleToken.cdc" import Collectibles from "../contracts/Collectibles.cdc" pub fun main(user: Address, id: UInt64): &NonFungibleToken.NFT? { let collectionCap= getAccount(user).capabilities .get<&{Collectibles.CollectionPublic}>(/public/NFTCollection) ?? panic("This public capability does not exist.") let collectionRef = collectionCap.borrow()! return collectionRef.borrowNFT(id: id) }


让我们逐行分解这段代码:


  1. 首先,我们导入NonFungibleTokenCollectibles合约。


  2. pub fun main(acctAddress: Address, id: UInt64): &NonFungibleToken.NFT?这一行定义了脚本的入口点,它是一个名为 main 的公共函数。该函数有两个参数:


  • acctAddressAddress类型参数,表示 Flow 区块链上帐户的地址。


  • idUInt64类型参数,表示集合中 NFT 的唯一标识符。


  1. 然后我们使用getCapability获取指定acctAddressCollectibles.Collection功能。能力是对允许访问其功能和数据的资源的引用。在本例中,它正在获取Collectibles.Collection资源类型的功能。


  2. 然后,我们使用borrowNFT函数从collectionRef中借用NFT。 borrowNFT函数采用id参数,该参数是集合中 NFT 的唯一标识符。功能上的borrow功能允许读取资源数据。


  3. 最后,我们从函数中返回 NFT。

第 3 步:测试网部署

现在,是时候将我们的智能合约部署到 Flow 测试网了。


1. 设置 Flow 账户。


在终端中运行以下命令生成 Flow 帐户:


 flow keys generate


请务必记下您的公钥和私钥。


接下来,我们将前往流量水龙头,根据我们的密钥创建一个新地址,并用一些测试代币为我们的帐户提供资金。请完成以下步骤来创建您的帐户:


  1. 将您的公钥粘贴到指定的输入字段中。


  2. 将签名和哈希算法设置为默认值。


  3. 完成验证码。


  4. 单击创建帐户。


设置帐户后,我们会收到与新 Flow 地址的对话,其中包含 1,000 个测试 Flow 代币。复制该地址,以便我们以后可以使用它


2. 配置项目。


现在,让我们配置我们的项目。最初,当我们设置项目时,它创建了一个flow.json文件。


这是 Flow CLI 的配置文件,定义 Flow CLI 可以为您执行的操作的配置。可以将其视为大致相当于以太坊上的hardhat.config.js


现在,打开代码编辑器,将以下代码复制并粘贴到flow.json文件中。


 { "contracts": { "Collectibles": "./cadence/contracts/Collectibles.cdc", "NonFungibleToken": { "source": "./cadence/contracts/NonFungibleToken.cdc", "aliases": { "testnet": "0x631e88ae7f1d7c20" } } }, "networks": { "testnet": "access.devnet.nodes.onflow.org:9000" }, "accounts": { "testnet-account": { "address": "ENTER YOUR ADDRESS FROM FAUCET HERE", "key": "ENTER YOUR GENERATED PRIVATE KEY HERE" } }, "deployments": { "testnet": { "testnet-account": [ "Collectibles" ] } } }


  1. 复制和粘贴。

将生成的私钥粘贴到代码中的位置(密钥:“在此处输入您生成的私钥”)。


  1. 执行。

现在,在测试网上执行代码。转到终端,然后运行以下代码:


 flow project deploy --network testnet


5. 等待确认。


提交交易后,您将收到交易 ID。等待交易在测试网上得到确认,表明智能合约已成功部署。


在此处检查您部署的合同。


GitHub上查看完整代码。

最后的想法和祝贺!

恭喜!您现在已经在 Flow 区块链上构建了一个收藏品门户并将其部署到测试网上。下一步是什么?现在,您可以构建我们将在本系列的第 2 部分中介绍的前端。


祝你有美好的一天!


也发布在这里