Smart Contract

MetadataViews

A.61fc4b873e58733b.MetadataViews

Deployed

1d ago
Feb 26, 2026, 09:44:01 PM UTC

Dependents

0 imports
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