查看原文
其他

技术社区分享|Cadence NFT 新标准 MetadataViews 介绍

Caos 福洛链 Flow Official 2023-07-20

本文由Flow技术大使Caos撰写。

2022 年 1 月 5 日, Flow NFT 仓库合并了 metadata 的新标准,本次 metadata 标准是由 Versus 与 Find 创始人Bjarte S. Karlsen 和 DapperLabs 工程师 Joshua Hannan 共同在 Flip-Fest 活动中完成,经过了将近三个月的讨论和完善,最终合并至 Flow 官方的 NFT 标准库中,本文将从代码的细节介绍 MetadataView 相关的标准细节。


写在前面

  • 本次标准升级是非强制升级的,不会影响 NonFungibleToken 合约,也不会影响原有部署的合约的业务逻辑

  • 已经部署在主网的合约需要升级才能应用新的 Metadata 标准

  • 升级所覆盖的资源会影响到 Collection 继承的类型,也会影响 NFT 资源

  • Metadata 标准定义了一套灵活的接口实现,可以支持任意类型或自定义的 Metadata 格式

  • 支持一个 NFT 拥有多种 Metadata 类型,并提供统一的读取方式

  • Metadata 标准是用一个全新的合约来定义,想要实现标准的合约需要在 NonFungibleToken 之外引入新的名为 MetadataViews 的新合约。



MetadataViews 合约详解

MetadataViews 又两个接口定义和四个推荐的 Metadata struct 组成(后续可能还会增补):

Interface

  • Resolver —— 由 NFT 资源继承

  • ResolverCollection —— 由存储 NFT 集合继承

Struct

  • Display

  • File

  • HTTPFile

  • IPFSFile


■ MetadataViews.Resolver

// A Resolver provides access to a set of metadata views.
//
// A struct or resource (e.g. an NFT) can implement this interface
// to provide access to the views that it supports.
//pub resource interface Resolver {
 pub fun getViews(): [Type] // getViews 返回 NFT 所支持的不同类型的 metadata 数据
 pub fun resolveView(_ view: Type): AnyStruct? // 根据具体的 View 类型获取到 NFT 所实现的 Struct
}

Resolver 需要由 NFT 或 NFT 的数据结构继承,并实现其中定义的两个函数

  • getViews —— 返回数组形式的 View 类型,也是 NFT 所支持的不同类型的 metadata 数据

  • resolveView —— 根据具体的类型,获得 NFT 的 struct 数据

// ExampleNFT// 1. 比原有的实现增加了 MetadataViews.Resolver 接口,并实现了其定义的两个函数pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
 pub let id: UInt64
 // 2. 为实现 MetadataViews.Display 所添加的属性,如果需要实现其他类型的 metadata 需要添加相应的字段  pub let name: String
 pub let description: String
 pub let thumbnail: String

 /* init function ... */
 pub fun getViews(): [Type] {
   return [
     Type<MetadataViews.Display>() // 3. 可以定义多个 View 类型
   ]
 }

 pub fun resolveView(_ view: Type): AnyStruct? {
   // 4. 根据 view 类型,返回不同的 struct 结构,当然我们也可以自己定义返回结构    switch view {
     case Type<MetadataViews.Display>():
       return MetadataViews.Display(
         name: self.name,
         description: self.description,
         thumbnail: MetadataViews.HTTPFile(
         url: self.thumbnail
       )
     )
   }
   return nil
 }
}
  • 注释一位置我们能看到需要让 NFT 资源实现 MetadataViews.Resolver 接口,实现了其定义的两个函数

  • 注释二位置实现了接口需要 NFT 能够返回指定类型的 Struct ,所以也需要定义返回结构所需要的字段,所以 NFT 增加了 MetadataViews.Display name、description 和 thumbnail

  • 注释三位置getViews 返回一个由类型组成的数组,这里只有一个 MetadataViews.Display 类型,当然我们也可以增加多个类型,比如 File、HTTPFile、IPFSFile,当然也要对应实现其对应的字段。

  • 注释四位置resolveView 会根据传入的 View 类型返回不同的数据结构,如果没有,则直接返回空,所以在编写查询脚本的时候需要先获通过 getViews 获得 NFT 支持的 View 类型,然后根据其支持的类型,调用 resolveView 获得 metadata 数据结构。


MetadataViews.ResolverCollection

/ A ResolverCollection is a group of view resolvers index by ID.
//
pub resource interface ResolverCollection {
 pub fun borrowViewResolver(id: UInt64): &{Resolver} // 通过 NFT id 获得 Resolver
 pub fun getIDs(): [UInt64] // 获得集合中的 NFT ids
}

ResolverCollection 需要由 Collection 资源继承,并实现其中的两个函数:

  • borrowViewResolver —— 通过具体的 NFT id 获得其支持的 Resolver 可以是任意类型 AnyResource

  • getIDs —— 获得集合中的 NFT ids

// 1. 增加 MetadataViews.ResolverCollection 继承并实现其函数pub resource Collection: ExampleNFTCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
 pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}  /* init function and props  */
 // getIDs returns an array of the IDs that are in the collection  pub fun getIDs(): [UInt64] {    return self.ownedNFTs.keys  }
 /* other function  */  // 2. 这里能够返回任意类型的 MetadataViews.Resolver 实现,拥有 getViews 和 resolveView 的方法获得 NFT 的数据  pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {    let nft = &self.ownedNFTs[id] as auth &NonFungibleToken.NFT    let exampleNFT = nft as! &ExampleNFT.NFT    return exampleNFT as &AnyResource{MetadataViews.Resolver}  }
 destroy() {    destroy self.ownedNFTs  }}

*注释一位置: NFT Collection 资源实现 MetadataViews.ResolverCollection 接口,实现了其定义的两个函数:我们一般在查询的时候也是通过获取到用户的 Collection 资源进行详情的查询,包括 getIDs 和获得 Resolver ,后面会在查询接口那里详述。

*注释二位置:borrowViewResolver 根据 NFT id 返回任意类型的 View Resolver 中的任意一种类型,其中包含 getViews 与 resolveView 方法


■  View Structs

MetadataViews.Display —— 基础的 metadata 实现,包含 NFT 基本信息

  • name

  • description

  • thumbnail —— 返回任意类型的 File 结构 —— 可以是 HTTPFile 或 IPFSFile

pub struct Display {
 // The name of the object.
 // This field will be displayed in lists and therefore should
 // be short an concise.
 pub let name: String
 // A written description of the object.
 // This field will be displayed in a detailed view of the object,
 // so can be more verbose (e.g. a paragraph instead of a single line).
 pub let description: String
 // A small thumbnail representation of the object.
 // This field should be a web-friendly file (i.e JPEG, PNG)
 // that can be displayed in lists, link previews, etc.
 pub let thumbnail: AnyStruct{File}

 init(
   name: String,
   description: String,
   thumbnail: AnyStruct{File}
 ) {
   self.name = name
   self.description = description
   self.thumbnail = thumbnail
 }
}

MetadataViews.File —— 基础 File 的结构接口,包含 uri 函数返回

// File is a generic interface that represents a file stored on or off chain.
// Files can be used to references images, videos and other media.
pub struct interface File {
 pub fun uri(): String
}

MetadataViews.HTTPFile —— 通过 Http 协议访问的图像 URL 信息

  • 包含 url 信息

// HTTPFile is a file that is accessible at an HTTP (or HTTPS) URL.
pub struct HTTPFile: File {
 pub let url: String

 init(url: String) {
   self.url = url
 }
 pub fun uri(): String {
   return self.url
 }
}

MetadataViews.IPFSFile —— 兼容 IPFS 协议的文件存储,包含 cid 和 path 的返回

  • cid

  • path

// IPFSThumbnail returns a thumbnail image for an object
// stored as an image file in IPFS.
// IPFS images are referenced by their content identifier (CID)
// rather than a direct URI. A client application can use this CID
// to find and load the image via an IPFS gateway.
pub struct IPFSFile: File {
 // CID is the content identifier for this IPFS file.
 // Ref: <https://docs.ipfs.io/concepts/content-addressing/>
 pub let cid: String
 // Path is an optional path to the file resource in an IPFS directory.
 // This field is only needed if the file is inside a directory.
 // Ref: <https://docs.ipfs.io/concepts/file-systems/>
 pub let path: String?

 init(cid: String, path: String?) {
   self.cid = cid
   self.path = path
 }
 // This function returns the IPFS native URL for this file.
 // Ref: <https://docs.ipfs.io/how-to/address-ipfs-on-web/#native-urls>
 pub fun uri(): String {
   if let path = self.path {
     return "ipfs://".concat(self.cid).concat("/").concat(path)
   }
   return "ipfs://".concat(self.cid)
   }
}



查询脚本

查询脚本需要进一步的对资源进行类型转换,才能获得基础的 metadata 信息,这里我们以 ExampleNFT 的查询脚本为例

import ExampleNFT from "../../contracts/ExampleNFT.cdc"
import MetadataViews from "./MetadataViews.cdc"

// 1. 定义 NFT 数据的读取结构,为脚本函数的返回值定义类型pub struct NFT {
 pub let name: String
 pub let description: String
 pub let thumbnail: String
 pub let owner: Address
 pub let type: String


 init(
   name: String,
   description: String,
   thumbnail: String,
   owner: Address,
   nftType: String,
 ) {
   self.name = name
   self.description = description
   self.thumbnail = thumbnail
   self.owner = owner
   self.type = nftType
 }
}

 // 2. 脚本执行函数,返回之前定义的 NFT 结构,传入地址 + NFT id 参数获取 NFT 数据  pub fun main(address: Address, id: UInt64): NFT {
   let account = getAccount(address)

   // 借用用户账户下的 Collection 资源    let collection = account
   .getCapability(ExampleNFT.CollectionPublicPath)
   .borrow<&{ExampleNFT.ExampleNFTCollectionPublic}>()
   ?? panic("Could not borrow a reference to the collection")

   // 通过 NFT id 获取到 NFT 资源    let nft = collection.borrowExampleNFT(id: id)!


   // Get the basic display information for this NFT    // 3. 因为我们的 NFT 已经实现了 MetadataViews.Resolver 接口, 所以这里调用 resolveView 能够获取到具体的 View 类型
   let view = nft.resolveView(Type<MetadataViews.Display>())!

   // 将返回的 `AnyStruct` 转换成具体的 struct    let display = view as! MetadataViews.Display    // 补充 NFT 所需要的额外信息    let owner: Address = nft.owner!.address!    // 获得 NFT 类型    let nftType = nft.getType()

   // 返回 NFT 结构    return NFT(
     name: display.name,
     description: display.description,
     thumbnail: display.thumbnail.uri(),
     owner: owner,
     nftType: nftType.identifier,
   )
}


*注释一位置:定义了一个 NFT 的数据结构,作为查询脚本的返回值

*注释二位置: 通过传入地址和 NFT id 作为参数,查询 NFT 的 metadata,这里需要我们提前知道该地址下所拥有的的 NFT IDs,通过 getIDs 就能获取到当前用户拥有的 NFT ids 然后查询

*注释三位置:因为我们的 NFT 已经实现了 MetadataViews.Resolver 接口, 所以这里调用 resolveView 能够获取到具体的 View 类型,在这之前也需要我们知道 NFT 具体实现了哪些 Struct,所以也需要我们通过 getViews 来获取。示例中因假设我们知道 NFT 实现了 MetadataViews.Display,所以使用了强制转换符号 ! 将 AnyStruct 转换成 view as! MetadataViews.Display


在实际使用的过程中,代码也许会更复杂一些,这里只是针对简单的 Example NFT 的实现进行针对性的查询,在整合的过程中还是需要具体的情况做具体的处理。



总结

新版的 Metadata 标准对 NFT 的影响很大,这也是为什么笔者决定特意写一篇介绍的原因,它可以帮助合约开发者、第三方的应用还有基础服务开发者按照统一的接口和规范实现 NFT 资产的数据查询。在以往没有标准的情况下,NFT 资产的多样性会导致查询复杂度提升,基于实现了 MetadataViews 标准的 NFT 合约,能够减轻基于 NFT 资产业务开发的复杂度,降低整合的成本,提高创新业务的开发效率,最重要的是能够让打开 NFT 资产可组合性的空间,类似于 ERC20 标准对 DeFi 的促进一样,Metadata 标准能够在资产可组合性上提供更多的可能。


更多细节可以参考JacobBjarte S. Karlsen现场英文视频讲解。

视频链接:https://www.youtube.com/watch?v=rgJRTozG3cw&t=404s&ab_channel=JacobTucker

 END 

什么是Flow福洛链?


Flow福洛链是一个快速,去中心化,且对开发者友好的区块链,旨在为新一代游戏、娱乐应用程序提供动力的数字资产的基础。Flow是唯一一个由始至终为消费者提供出色体验的Layer-1区块链团队。其团队创造的dApp包括:CryptoKittiesDapper WalletsNBA Top shot


CrytoKitties于2017年推出时便快速成为加密市场最受欢迎的dApp,因其成功而导致以太坊堵塞。在Flow上运营的NBA Top shot也已成为增长最快的dApp,在公开发布后的6个月创造了7亿美金销量。正因为Flow公链的可扩展性和消费者友好的体验,让这一切成为可能。目前有1000多个项目正在Flow链上筹备中,我们期待看到一个伟大的生态系统蓬勃发展。


关于Dapper Labs

Dapper Labs是一家位于加拿大的全球顶尖区块链服务商,在2017 年年底通过CryptoKitties收藏游戏成功进入⽤户视野,并且因为加密猫的爆⽕导致以太坊拥堵,从而推出Flow公链以及全新的开发语言—— Cadence,旨在吸引更多的开发者在Flow 上开发应⽤。 


Flow的合作伙伴们:

我们欢迎越来越多的小伙伴加入Flow星球,为星球增添色彩!


Flow 官网:https://zh.onflow.org/

Flow 论坛: https://forum.onflow.org/

Flow Discord:

https://discord.com/invite/flow

Flow CN Telegram: https://t.me/flow_zh

Flow B站:https://space.bilibili.com/1002168058

Flow 微博: 

https://weibo.com/7610419699

Flow CSDN:

https://blog.csdn.net/weixin_57551966?spm=1010.2135.3001.5343


扫码添加Flow官方账号微信号,加入Flow生态群

微信号 : FlowChainOfficial

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存