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 }