github.com/DapperCollectives/CAST/backend@v0.0.0-20230921221157-1350c8be7c96/main/cadence/contracts/MetadataViews.cdc (about)

     1  /**
     2  
     3  This contract implements the metadata standard proposed
     4  in FLIP-0636.
     5  
     6  Ref: https://github.com/onflow/flow/blob/master/flips/20210916-nft-metadata.md
     7  
     8  Structs and resources can implement one or more
     9  metadata types, called views. Each view type represents
    10  a different kind of metadata, such as a creator biography
    11  or a JPEG image file.
    12  */
    13  
    14  import FungibleToken from 0xee82856bf20e2aa6
    15  import NonFungibleToken from 0xf8d6e0586b0a20c7
    16  
    17  pub contract MetadataViews {
    18  
    19      /// A Resolver provides access to a set of metadata views.
    20      ///
    21      /// A struct or resource (e.g. an NFT) can implement this interface
    22      /// to provide access to the views that it supports.
    23      ///
    24      pub resource interface Resolver {
    25          pub fun getViews(): [Type]
    26          pub fun resolveView(_ view: Type): AnyStruct?
    27      }
    28  
    29      /// A ResolverCollection is a group of view resolvers index by ID.
    30      ///
    31      pub resource interface ResolverCollection {
    32          pub fun borrowViewResolver(id: UInt64): &{Resolver}
    33          pub fun getIDs(): [UInt64]
    34      }
    35  
    36      /// NFTView is a group of views used to give a complete picture of an NFT
    37      ///
    38      pub struct NFTView {
    39          pub let id: UInt64
    40          pub let uuid: UInt64
    41          pub let display: Display?
    42          pub let externalURL: ExternalURL?
    43          pub let collectionData: NFTCollectionData?
    44          pub let collectionDisplay: NFTCollectionDisplay?
    45          pub let royalties: Royalties?
    46          pub let traits: Traits?
    47  
    48          init(
    49              id : UInt64,
    50              uuid : UInt64,
    51              display : Display?,
    52              externalURL : ExternalURL?,
    53              collectionData : NFTCollectionData?,
    54              collectionDisplay : NFTCollectionDisplay?,
    55              royalties : Royalties?,
    56              traits: Traits?
    57          ) {
    58              self.id = id
    59              self.uuid = uuid
    60              self.display = display
    61              self.externalURL = externalURL
    62              self.collectionData = collectionData
    63              self.collectionDisplay = collectionDisplay
    64              self.royalties = royalties
    65              self.traits = traits
    66          }
    67      }
    68  
    69      /// Display is a basic view that includes the name, description and
    70      /// thumbnail for an object. Most objects should implement this view.
    71      ///
    72      pub struct Display {
    73  
    74          /// The name of the object. 
    75          ///
    76          /// This field will be displayed in lists and therefore should
    77          /// be short an concise.
    78          ///
    79          pub let name: String
    80  
    81          /// A written description of the object. 
    82          ///
    83          /// This field will be displayed in a detailed view of the object,
    84          /// so can be more verbose (e.g. a paragraph instead of a single line).
    85          ///
    86          pub let description: String
    87  
    88          /// A small thumbnail representation of the object.
    89          ///
    90          /// This field should be a web-friendly file (i.e JPEG, PNG)
    91          /// that can be displayed in lists, link previews, etc.
    92          ///
    93          pub let thumbnail: AnyStruct{File}
    94  
    95          init(
    96              name: String,
    97              description: String,
    98              thumbnail: AnyStruct{File}
    99          ) {
   100              self.name = name
   101              self.description = description
   102              self.thumbnail = thumbnail
   103          }
   104      }
   105  
   106      /// A helper to get Display in a typesafe way
   107      pub fun getDisplay(_ viewResolver: &{Resolver}) : Display? {
   108          if let view = viewResolver.resolveView(Type<Display>()) {
   109              if let v = view as? Display {
   110                  return v
   111              }
   112          }
   113          return nil
   114      }
   115  
   116      /// File is a generic interface that represents a file stored on or off chain.
   117      ///
   118      /// Files can be used to references images, videos and other media.
   119      ///
   120      pub struct interface File {
   121          pub fun uri(): String
   122      }
   123  
   124      /// HTTPFile is a file that is accessible at an HTTP (or HTTPS) URL. 
   125      ///
   126      pub struct HTTPFile: File {
   127          pub let url: String
   128  
   129          init(url: String) {
   130              self.url = url
   131          }
   132  
   133          pub fun uri(): String {
   134              return self.url
   135          }
   136      }
   137  
   138      /// IPFSFile returns a thumbnail image for an object
   139      /// stored as an image file in IPFS.
   140      ///
   141      /// IPFS images are referenced by their content identifier (CID)
   142      /// rather than a direct URI. A client application can use this CID
   143      /// to find and load the image via an IPFS gateway.
   144      ///
   145      pub struct IPFSFile: File {
   146  
   147          /// CID is the content identifier for this IPFS file.
   148          ///
   149          /// Ref: https://docs.ipfs.io/concepts/content-addressing/
   150          ///
   151          pub let cid: String
   152  
   153          /// Path is an optional path to the file resource in an IPFS directory.
   154          ///
   155          /// This field is only needed if the file is inside a directory.
   156          ///
   157          /// Ref: https://docs.ipfs.io/concepts/file-systems/
   158          ///
   159          pub let path: String?
   160  
   161          init(cid: String, path: String?) {
   162              self.cid = cid
   163              self.path = path
   164          }
   165  
   166          /// This function returns the IPFS native URL for this file.
   167          ///
   168          /// Ref: https://docs.ipfs.io/how-to/address-ipfs-on-web/#native-urls
   169          ///
   170          pub fun uri(): String {
   171              if let path = self.path {
   172                  return "ipfs://".concat(self.cid).concat("/").concat(path)
   173              }
   174  
   175              return "ipfs://".concat(self.cid)
   176          }
   177      }
   178  
   179      /// Editions is an optional view for collections that issues multiple objects
   180      /// with the same or similar metadata, for example an X of 100 set. This information is 
   181      /// useful for wallets and marketplaes.
   182      ///
   183      /// An NFT might be part of multiple editions, which is why the edition information
   184      /// is returned as an arbitrary sized array
   185      /// 
   186      pub struct Editions {
   187  
   188          /// An arbitrary-sized list for any number of editions
   189          /// that the NFT might be a part of
   190          pub let infoList: [Edition]
   191  
   192          init(_ infoList: [Edition]) {
   193              self.infoList = infoList
   194          }
   195      }
   196  
   197      /// A helper to get Editions in a typesafe way
   198      pub fun getEditions(_ viewResolver: &{Resolver}) : Editions? {
   199          if let view = viewResolver.resolveView(Type<Editions>()) {
   200              if let v = view as? Editions {
   201                  return v
   202              }
   203          }
   204          return nil
   205      }
   206  
   207      /// Edition information for a single edition
   208      pub struct Edition {
   209  
   210          /// The name of the edition
   211          /// For example, this could be Set, Play, Series,
   212          /// or any other way a project could classify its editions
   213          pub let name: String?
   214  
   215          /// The edition number of the object.
   216          ///
   217          /// For an "24 of 100 (#24/100)" item, the number is 24. 
   218          ///
   219          pub let number: UInt64
   220  
   221          /// The max edition number of this type of objects.
   222          /// 
   223          /// This field should only be provided for limited-editioned objects.
   224          /// For an "24 of 100 (#24/100)" item, max is 100.
   225          /// For an item with unlimited edition, max should be set to nil.
   226          /// 
   227          pub let max: UInt64?
   228  
   229          init(name: String?, number: UInt64, max: UInt64?) {
   230              if max != nil {
   231                  assert(number <= max!, message: "The number cannot be greater than the max number!")
   232              }
   233              self.name = name
   234              self.number = number
   235              self.max = max
   236          }
   237      }
   238  
   239  
   240      /// A view representing a project-defined serial number for a specific NFT
   241      /// Projects have different definitions for what a serial number should be
   242      /// Some may use the NFTs regular ID and some may use a different classification system
   243      /// The serial number is expected to be unique among other NFTs within that project
   244      ///
   245      pub struct Serial {
   246          pub let number: UInt64
   247  
   248          init(_ number: UInt64) {
   249              self.number = number
   250          }
   251      }
   252  
   253      /// A helper to get Serial in a typesafe way
   254      pub fun getSerial(_ viewResolver: &{Resolver}) : Serial? {
   255          if let view = viewResolver.resolveView(Type<Serial>()) {
   256              if let v = view as? Serial {
   257                  return v
   258              }
   259          }
   260          return nil
   261      }
   262  
   263      /*
   264      *  Royalty Views
   265      *  Defines the composable royalty standard that gives marketplaces a unified interface
   266      *  to support NFT royalties.
   267      *
   268      *  Marketplaces can query this `Royalties` struct from NFTs 
   269      *  and are expected to pay royalties based on these specifications.
   270      *
   271      */
   272      pub struct Royalties {
   273  
   274          /// Array that tracks the individual royalties
   275          access(self) let cutInfos: [Royalty]
   276  
   277          pub init(_ cutInfos: [Royalty]) {
   278              // Validate that sum of all cut multipliers should not be greater than 1.0
   279              var totalCut = 0.0
   280              for royalty in cutInfos {
   281                  totalCut = totalCut + royalty.cut
   282              }
   283              assert(totalCut <= 1.0, message: "Sum of cutInfos multipliers should not be greater than 1.0")
   284              // Assign the cutInfos
   285              self.cutInfos = cutInfos
   286          }
   287  
   288          /// Return the cutInfos list
   289          pub fun getRoyalties(): [Royalty] {
   290              return self.cutInfos
   291          }
   292      }
   293  
   294      /// A helper to get Royalties in a typesafe way
   295      pub fun getRoyalties(_ viewResolver: &{Resolver}) : Royalties? {
   296          if let view = viewResolver.resolveView(Type<Royalties>()) {
   297              if let v = view as? Royalties {
   298                  return v
   299              }
   300          }
   301          return nil
   302      }
   303  
   304      /// Struct to store details of a single royalty cut for a given NFT
   305      pub struct Royalty {
   306  
   307          /// Generic FungibleToken Receiver for the beneficiary of the royalty
   308          /// Can get the concrete type of the receiver with receiver.getType()
   309          /// Recommendation - Users should create a new link for a FlowToken receiver for this using `getRoyaltyReceiverPublicPath()`,
   310          /// and not use the default FlowToken receiver.
   311          /// This will allow users to update the capability in the future to use a more generic capability
   312          pub let receiver: Capability<&AnyResource{FungibleToken.Receiver}>
   313  
   314          /// Multiplier used to calculate the amount of sale value transferred to royalty receiver.
   315          /// Note - It should be between 0.0 and 1.0 
   316          /// Ex - If the sale value is x and multiplier is 0.56 then the royalty value would be 0.56 * x.
   317          ///
   318          /// Generally percentage get represented in terms of basis points
   319          /// in solidity based smart contracts while cadence offers `UFix64` that already supports
   320          /// the basis points use case because its operations
   321          /// are entirely deterministic integer operations and support up to 8 points of precision.
   322          pub let cut: UFix64
   323  
   324          /// Optional description: This can be the cause of paying the royalty,
   325          /// the relationship between the `wallet` and the NFT, or anything else that the owner might want to specify
   326          pub let description: String
   327  
   328          init(recepient: Capability<&AnyResource{FungibleToken.Receiver}>, cut: UFix64, description: String) {
   329              pre {
   330                  cut >= 0.0 && cut <= 1.0 : "Cut value should be in valid range i.e [0,1]"
   331              }
   332              self.receiver = recepient
   333              self.cut = cut
   334              self.description = description
   335          }
   336      }
   337  
   338      /// Get the path that should be used for receiving royalties
   339      /// This is a path that will eventually be used for a generic switchboard receiver,
   340      /// hence the name but will only be used for royalties for now.
   341      pub fun getRoyaltyReceiverPublicPath(): PublicPath {
   342          return /public/GenericFTReceiver
   343      }
   344  
   345      /// Medias is an optional view for collections that issue objects with multiple Media sources in it
   346      ///
   347      pub struct Medias {
   348  
   349          /// An arbitrary-sized list for any number of Media items
   350          pub let items: [Media]
   351  
   352          init(_ items: [Media]) {
   353              self.items = items
   354          }
   355      }
   356  
   357      /// A helper to get Medias in a typesafe way
   358      pub fun getMedias(_ viewResolver: &{Resolver}) : Medias? {
   359          if let view = viewResolver.resolveView(Type<Medias>()) {
   360              if let v = view as? Medias {
   361                  return v
   362              }
   363          }
   364          return nil
   365      }
   366  
   367      /// A view to represent Media, a file with an correspoiding mediaType.
   368      pub struct Media {
   369  
   370          /// File for the media
   371          pub let file: AnyStruct{File}
   372  
   373          /// media-type comes on the form of type/subtype as described here https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
   374          pub let mediaType: String
   375  
   376          init(file: AnyStruct{File}, mediaType: String) {
   377            self.file=file
   378            self.mediaType=mediaType
   379          }
   380      }
   381  
   382      /// A license according to https://spdx.org/licenses/
   383      ///
   384      /// This view can be used if the content of an NFT is licensed. 
   385      pub struct License {
   386          pub let spdxIdentifier: String
   387  
   388          init(_ identifier: String) {
   389              self.spdxIdentifier = identifier
   390          }
   391      }
   392  
   393      /// A helper to get License in a typesafe way
   394      pub fun getLicense(_ viewResolver: &{Resolver}) : License? {
   395          if let view = viewResolver.resolveView(Type<License>()) {
   396              if let v = view as? License {
   397                  return v
   398              }
   399          }
   400          return nil
   401      }
   402  
   403  
   404      /// A view to expose a URL to this item on an external site.
   405      ///
   406      /// This can be used by applications like .find and Blocto to direct users to the original link for an NFT.
   407      pub struct ExternalURL {
   408          pub let url: String
   409  
   410          init(_ url: String) {
   411              self.url=url
   412          }
   413      }
   414  
   415      /// A helper to get ExternalURL in a typesafe way
   416      pub fun getExternalURL(_ viewResolver: &{Resolver}) : ExternalURL? {
   417          if let view = viewResolver.resolveView(Type<ExternalURL>()) {
   418              if let v = view as? ExternalURL {
   419                  return v
   420              }
   421          }
   422          return nil
   423      }
   424  
   425      // A view to expose the information needed store and retrieve an NFT
   426      //
   427      // This can be used by applications to setup a NFT collection with proper storage and public capabilities.
   428      pub struct NFTCollectionData {
   429          /// Path in storage where this NFT is recommended to be stored.
   430          pub let storagePath: StoragePath
   431  
   432          /// Public path which must be linked to expose public capabilities of this NFT
   433          /// including standard NFT interfaces and metadataviews interfaces
   434          pub let publicPath: PublicPath
   435  
   436          /// Private path which should be linked to expose the provider
   437          /// capability to withdraw NFTs from the collection holding NFTs
   438          pub let providerPath: PrivatePath
   439  
   440          /// Public collection type that is expected to provide sufficient read-only access to standard
   441          /// functions (deposit + getIDs + borrowNFT)
   442          /// This field is for backwards compatibility with collections that have not used the standard
   443          /// NonFungibleToken.CollectionPublic interface when setting up collections. For new
   444          /// collections, this may be set to be equal to the type specified in `publicLinkedType`.
   445          pub let publicCollection: Type
   446  
   447          /// Type that should be linked at the aforementioned public path. This is normally a
   448          /// restricted type with many interfaces. Notably the `NFT.CollectionPublic`,
   449          /// `NFT.Receiver`, and `MetadataViews.ResolverCollection` interfaces are required.
   450          pub let publicLinkedType: Type
   451  
   452          /// Type that should be linked at the aforementioned private path. This is normally
   453          /// a restricted type with at a minimum the `NFT.Provider` interface
   454          pub let providerLinkedType: Type
   455  
   456          /// Function that allows creation of an empty NFT collection that is intended to store
   457          /// this NFT.
   458          pub let createEmptyCollection: ((): @NonFungibleToken.Collection)
   459  
   460          init(
   461              storagePath: StoragePath,
   462              publicPath: PublicPath,
   463              providerPath: PrivatePath,
   464              publicCollection: Type,
   465              publicLinkedType: Type,
   466              providerLinkedType: Type,
   467              createEmptyCollectionFunction: ((): @NonFungibleToken.Collection)
   468          ) {
   469              pre {
   470                  publicLinkedType.isSubtype(of: Type<&{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>()): "Public type must include NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, and MetadataViews.ResolverCollection interfaces."
   471                  providerLinkedType.isSubtype(of: Type<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection}>()): "Provider type must include NonFungibleToken.Provider, NonFungibleToken.CollectionPublic, and MetadataViews.ResolverCollection interface."
   472              }
   473              self.storagePath=storagePath
   474              self.publicPath=publicPath
   475              self.providerPath = providerPath
   476              self.publicCollection=publicCollection
   477              self.publicLinkedType=publicLinkedType
   478              self.providerLinkedType = providerLinkedType
   479              self.createEmptyCollection=createEmptyCollectionFunction
   480          }
   481      }
   482  
   483      /// A helper to get NFTCollectionData in a way that will return an typed Optional
   484      pub fun getNFTCollectionData(_ viewResolver: &{Resolver}) : NFTCollectionData? {
   485          if let view = viewResolver.resolveView(Type<NFTCollectionData>()) {
   486              if let v = view as? NFTCollectionData {
   487                  return v
   488              }
   489          }
   490          return nil
   491      }
   492  
   493      // A view to expose the information needed to showcase this NFT's collection
   494      //
   495      // This can be used by applications to give an overview and graphics of the NFT collection
   496      // this NFT belongs to.
   497      pub struct NFTCollectionDisplay {
   498          // Name that should be used when displaying this NFT collection.
   499          pub let name: String
   500  
   501          // Description that should be used to give an overview of this collection.
   502          pub let description: String
   503  
   504          // External link to a URL to view more information about this collection.
   505          pub let externalURL: ExternalURL
   506  
   507          // Square-sized image to represent this collection.
   508          pub let squareImage: Media
   509  
   510          // Banner-sized image for this collection, recommended to have a size near 1200x630.
   511          pub let bannerImage: Media
   512  
   513          // Social links to reach this collection's social homepages.
   514          // Possible keys may be "instagram", "twitter", "discord", etc.
   515          pub let socials: {String: ExternalURL}
   516  
   517          init(
   518              name: String,
   519              description: String,
   520              externalURL: ExternalURL,
   521              squareImage: Media,
   522              bannerImage: Media,
   523              socials: {String: ExternalURL}
   524          ) {
   525              self.name = name
   526              self.description = description
   527              self.externalURL = externalURL
   528              self.squareImage = squareImage
   529              self.bannerImage = bannerImage
   530              self.socials = socials
   531          }
   532      }
   533  
   534      /// A helper to get NFTCollectionDisplay in a way that will return an typed Optional
   535      pub fun getNFTCollectionDisplay(_ viewResolver: &{Resolver}) : NFTCollectionDisplay? {
   536          if let view = viewResolver.resolveView(Type<NFTCollectionDisplay>()) {
   537              if let v = view as? NFTCollectionDisplay {
   538                  return v
   539              }
   540          }
   541          return nil
   542      }
   543  
   544      // A view to represent a single field of metadata on an NFT.
   545      //
   546      // This is used to get traits of individual key/value pairs along with some contextualized data about the trait
   547      pub struct Trait {
   548          // The name of the trait. Like Background, Eyes, Hair, etc.
   549          pub let name: String
   550  
   551          // The underlying value of the trait, the rest of the fields of a trait provide context to the value.
   552          pub let value: AnyStruct
   553  
   554          // displayType is used to show some context about what this name and value represent
   555          // for instance, you could set value to a unix timestamp, and specify displayType as "Date" to tell
   556          // platforms to consume this trait as a date and not a number
   557          pub let displayType: String?
   558  
   559          // Rarity can also be used directly on an attribute.
   560          //
   561          // This is optional because not all attributes need to contribute to the NFT's rarity.
   562          pub let rarity: Rarity?
   563  
   564          init(name: String, value: AnyStruct, displayType: String?, rarity: Rarity?) {
   565              self.name = name
   566              self.value = value
   567              self.displayType = displayType
   568              self.rarity = rarity
   569          }
   570      }
   571  
   572      // A view to return all the traits on an NFT.
   573      //
   574      // This is used to return traits as individual key/value pairs along with some contextualized data about each trait.
   575      pub struct Traits {
   576          pub let traits: [Trait]
   577  
   578          init(_ traits: [Trait]) {
   579              self.traits = traits
   580          }
   581  
   582          pub fun addTrait(_ t: Trait) {
   583              self.traits.append(t)
   584          }
   585      }
   586  
   587      /// A helper to get Traits view in a typesafe way
   588      pub fun getTraits(_ viewResolver: &{Resolver}) : Traits? {
   589          if let view = viewResolver.resolveView(Type<Traits>()) {
   590              if let v = view as? Traits {
   591                  return v
   592              }
   593          }
   594          return nil
   595      }
   596  
   597      // A helper function to easily convert a dictionary to traits. For NFT collections that do not need either of the
   598      // optional values of a Trait, this method should suffice to give them an array of valid traits.
   599      pub fun dictToTraits(dict: {String: AnyStruct}, excludedNames: [String]?): Traits {
   600          // Collection owners might not want all the fields in their metadata included.
   601          // They might want to handle some specially, or they might just not want them included at all.
   602          if excludedNames != nil {
   603              for k in excludedNames! {
   604                  dict.remove(key: k)
   605              }
   606          }
   607  
   608          let traits: [Trait] = []
   609          for k in dict.keys {
   610              let trait = Trait(name: k, value: dict[k]!, displayType: nil, rarity: nil)
   611              traits.append(trait)
   612          }
   613  
   614          return Traits(traits)
   615      }
   616  
   617      /// Rarity information for a single rarity
   618      //
   619      /// Note that a rarity needs to have either score or description but it can have both
   620      pub struct Rarity {
   621          /// The score of the rarity as a number
   622          ///
   623          pub let score: UFix64?
   624  
   625          /// The maximum value of score
   626          ///
   627          pub let max: UFix64?
   628  
   629          /// The description of the rarity as a string.
   630          ///
   631          /// This could be Legendary, Epic, Rare, Uncommon, Common or any other string value
   632          pub let description: String?
   633  
   634          init(score: UFix64?, max: UFix64?, description: String?) {
   635              if score == nil && description == nil {
   636                  panic("A Rarity needs to set score, description or both")
   637              }
   638  
   639              self.score = score
   640              self.max = max
   641              self.description = description
   642          }
   643      }
   644  
   645      /// A helper to get Rarity view in a typesafe way
   646      pub fun getRarity(_ viewResolver: &{Resolver}) : Rarity? {
   647          if let view = viewResolver.resolveView(Type<Rarity>()) {
   648              if let v = view as? Rarity {
   649                  return v
   650              }
   651          }
   652          return nil
   653      }
   654  
   655      pub fun getNFTView(id: UInt64, viewResolver: &{Resolver}) : NFTView {
   656          return NFTView(
   657              id : id,
   658              uuid: viewResolver.uuid,
   659              display: self.getDisplay(viewResolver),
   660              externalURL : self.getExternalURL(viewResolver),
   661              collectionData : self.getNFTCollectionData(viewResolver),
   662              collectionDisplay : self.getNFTCollectionDisplay(viewResolver),
   663              royalties : self.getRoyalties(viewResolver),
   664              traits : self.getTraits(viewResolver)
   665          )
   666      }
   667  
   668  }