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