Smart Contract

NFTViewUtils

A.15a918087ab12d86.NFTViewUtils

Valid From

86,819,238

Deployed

3d ago
Feb 24, 2026, 11:42:13 PM UTC

Dependents

2 imports
1/**
2> Author: Fixes Lab <https://github.com/fixes-world/>
3
4# NFT List - An on-chain list of Flow Standard Non-Fungible Tokens (NFTs).
5
6This is the Non-Fungible Token view utilties contract of the Token List.
7
8*/
9import MetadataViews from 0x1d7e57aa55817448
10import ViewResolver from 0x1d7e57aa55817448
11import NonFungibleToken from 0x1d7e57aa55817448
12// TokenList Imports
13import FTViewUtils from 0x15a918087ab12d86
14
15access(all) contract NFTViewUtils {
16
17    // ----- Entitlement -----
18
19    // An entitlement for allowing edit the FT View Data
20    access(all) entitlement Editable
21
22    /*  ---- Events ---- */
23
24    access(all) event NFTDisplayUpdated(
25        address: Address,
26        contractName: String,
27        name: String?,
28        description: String?,
29        externalURL: String?,
30        squareImage: String?,
31        bannerImage: String?,
32        socials: {String: String}
33    )
34
35    /// The struct for the Fungible Token Identity
36    ///
37    access(all) struct NFTIdentity: FTViewUtils.TokenIdentity {
38        access(all)
39        let address: Address
40        access(all)
41        let contractName: String
42
43        view init(
44            _ address: Address,
45            _ contractName: String
46        ) {
47            self.address = address
48            self.contractName = contractName
49        }
50
51        access(all)
52        view fun buildType(): Type {
53            return self.buildCollectionType()
54        }
55
56        access(all)
57        view fun buildCollectionType(): Type {
58            return NFTViewUtils.buildCollectionType(self.address, self.contractName)
59                ?? panic("Could not build the FT Type")
60        }
61
62        access(all)
63        view fun buildNFTType(): Type {
64            return NFTViewUtils.buildNFTType(self.address, self.contractName)
65                ?? panic("Could not build the FT Type")
66        }
67
68        /// Borrow the Fungible Token Contract
69        ///
70        access(all)
71        fun borrowNFTContract(): &{NonFungibleToken} {
72            return getAccount(self.address)
73                .contracts.borrow<&{NonFungibleToken}>(name: self.contractName)
74                ?? panic("Could not borrow the FungibleToken contract reference")
75        }
76    }
77
78    /// The struct for the Fungible Token Display with Source
79    ///
80    access(all) struct NFTCollectionViewWithSource {
81        access(all)
82        let source: Address?
83        access(all)
84        let display: MetadataViews.NFTCollectionDisplay
85
86        view init(
87            _ source: Address?,
88            _ display: MetadataViews.NFTCollectionDisplay
89        ) {
90            self.source = source
91            self.display = display
92        }
93    }
94
95    /// The struct for the Fungible Token Paths
96    ///
97    access(all) struct StandardNFTPaths {
98        access(all)
99        let storagePath: StoragePath
100        access(all)
101        let publicPath: PublicPath
102
103        view init(
104            _ storagePath: StoragePath,
105            _ publicPath: PublicPath,
106        ) {
107            self.storagePath = storagePath
108            self.publicPath = publicPath
109        }
110    }
111
112    /// The struct for the Fungible Token List View
113    ///
114    access(all) struct StandardTokenView: FTViewUtils.ITokenView {
115        access(all)
116        let identity: NFTIdentity
117        access(all)
118        let tags: [String]
119        access(all)
120        let dataSource: Address?
121        access(all)
122        let paths: StandardNFTPaths?
123        access(all)
124        let display: NFTCollectionViewWithSource?
125
126        view init(
127            identity: NFTIdentity,
128            tags: [String],
129            dataSource: Address?,
130            paths: StandardNFTPaths?,
131            display: NFTCollectionViewWithSource?,
132        ) {
133            self.identity = identity
134            self.tags = tags
135            self.dataSource = dataSource
136            self.paths = paths
137            self.display = display
138        }
139
140        access(all)
141        view fun getIdentity(): {FTViewUtils.TokenIdentity} {
142            return self.identity
143        }
144    }
145
146    /// The struct for the Bridged Token List View
147    ///
148    access(all) struct BridgedTokenView: FTViewUtils.IBridgedTokenView, FTViewUtils.ITokenView {
149        access(all)
150        let identity: NFTIdentity
151        access(all)
152        let evmAddress: String
153        access(all)
154        let tags: [String]
155        access(all)
156        let dataSource: Address?
157        access(all)
158        let paths: StandardNFTPaths?
159        access(all)
160        let display: NFTCollectionViewWithSource?
161
162        view init(
163            identity: NFTIdentity,
164            evmAddress: String,
165            tags: [String],
166            dataSource: Address?,
167            paths: StandardNFTPaths?,
168            display: NFTCollectionViewWithSource?,
169        ) {
170            self.identity = identity
171            self.evmAddress = evmAddress
172            self.tags = tags
173            self.dataSource = dataSource
174            self.paths = paths
175            self.display = display
176        }
177
178        access(all)
179        view fun getIdentity(): {FTViewUtils.TokenIdentity} {
180            return self.identity
181        }
182    }
183
184    /** Editable NFTView */
185
186    /// The interface for the Editable FT View Display
187    ///
188    access(all) resource interface EditableNFTCollectionDisplayInterface: ViewResolver.Resolver {
189        /// Identity of the FT
190        access(all)
191        let identity: NFTIdentity
192        // ----- FT Display -----
193        access(all)
194        view fun getName(): String?
195        access(all)
196        view fun getDescription(): String?
197        access(all)
198        view fun getExternalURL(): MetadataViews.ExternalURL?
199        access(all)
200        view fun getSocials(): {String: MetadataViews.ExternalURL}
201        // Square-sized image to represent this collection.
202        access(all)
203        view fun getSquareImage(): MetadataViews.Media?
204        // Banner-sized image for this collection, recommended to have a size near 1200x630.
205        access(all)
206        view fun getBannerImage(): MetadataViews.Media?
207        // Get all the images
208        access(all)
209        fun getImages(): MetadataViews.Medias
210        // --- default implementation ---
211        // Get the default square image
212        access(all)
213        view fun getDefaultSquareImage(): MetadataViews.Media {
214            return MetadataViews.Media(
215                file: MetadataViews.HTTPFile(url: "https://i.imgur.com/hs3U5CY.png"),
216                mediaType: "image/png"
217            )
218        }
219        // Get the default banner image
220        //
221        access(all)
222        view fun getDefaultBannerImage(): MetadataViews.Media {
223            return MetadataViews.Media(
224                file: MetadataViews.HTTPFile(url: "https://i.imgur.com/4DOuqFf.jpeg"),
225                mediaType: "image/jpeg"
226            )
227        }
228        /// Get the FT Display
229        ///
230        access(all)
231        fun getCollectionDisplay(): MetadataViews.NFTCollectionDisplay {
232            return MetadataViews.NFTCollectionDisplay(
233                name: self.getName() ?? "Unknown Token",
234                description: self.getDescription() ?? "No Description",
235                externalURL: self.getExternalURL() ?? MetadataViews.ExternalURL("https://fixes.world"),
236                // Square-sized image to represent this collection.
237                squareImage: self.getSquareImage() ?? self.getDefaultSquareImage(),
238                // Banner-sized image for this collection, recommended to have a size near 1200x630.
239                bannerImage: self.getBannerImage() ?? self.getDefaultBannerImage(),
240                socials: self.getSocials()
241            )
242        }
243    }
244
245    /// The interface for the FT View Display Editor
246    ///
247    access(all) resource interface NFTCollectionDisplayEditor: EditableNFTCollectionDisplayInterface {
248        /// Set the FT Display
249        access(Editable)
250        fun setDisplay(
251            name: String?,
252            description: String?,
253            externalURL: String?,
254            squareImage: String?,
255            bannerImage: String?,
256            socials: {String: String}
257        )
258    }
259
260    /// The Resource for the FT Display
261    ///
262    access(all) resource EditableNFTCollectionDisplay: NFTCollectionDisplayEditor, EditableNFTCollectionDisplayInterface {
263        access(all)
264        let identity: NFTIdentity
265        access(contract)
266        let metadata: {String: String}
267
268        init(
269            _ address: Address,
270            _ contractName: String,
271        ) {
272            self.identity = NFTIdentity(address, contractName)
273            self.metadata = {}
274            // ensure identity is valid
275            self.identity.borrowNFTContract()
276        }
277
278        /// Set the FT Display
279        ///
280        access(Editable)
281        fun setDisplay(
282            name: String?,
283            description: String?,
284            externalURL: String?,
285            squareImage: String?,
286            bannerImage: String?,
287            socials: {String: String}
288        ) {
289            // set name
290            if name != nil {
291                self.metadata["name"] = name!
292            }
293            // set description
294            if description != nil {
295                self.metadata["description"] = description!
296            }
297            // set external URL
298            if externalURL != nil {
299                self.metadata["externalURL"] = externalURL!
300            }
301            // set squareImage
302            if squareImage != nil {
303                // the last 3 chars are file type, check the type
304                let fileType = squareImage!.slice(from: squareImage!.length - 3, upTo: squareImage!.length)
305                if fileType == "png" {
306                    self.metadata["image:square:png"] = squareImage!
307                } else if fileType == "svg" {
308                    self.metadata["image:square:svg"] = squareImage!
309                } else if fileType == "jpg" {
310                    self.metadata["image:square:jpg"] = squareImage!
311                } else {
312                    self.metadata["image:square"] = squareImage!
313                }
314            }
315            // set bannerImage
316            if bannerImage != nil {
317                // the last 3 chars are file type, check the type
318                let fileType = bannerImage!.slice(from: bannerImage!.length - 3, upTo: bannerImage!.length)
319                if fileType == "png" {
320                    self.metadata["image:banner:png"] = bannerImage!
321                } else if fileType == "svg" {
322                    self.metadata["image:banner:svg"] = bannerImage!
323                } else if fileType == "jpg" {
324                    self.metadata["image:banner:jpg"] = bannerImage!
325                } else {
326                    self.metadata["image:banner"] = bannerImage!
327                }
328            }
329            // set socials
330            for key in socials.keys {
331                self.metadata["social:".concat(key)] = socials[key]!
332            }
333
334            // Emit the event
335            emit NFTDisplayUpdated(
336                address: self.identity.address,
337                contractName: self.identity.contractName,
338                name: name,
339                description: description,
340                externalURL: externalURL,
341                squareImage: squareImage,
342                bannerImage: bannerImage,
343                socials: socials
344            )
345        }
346
347        /** ---- Implement the EditableFTViewDisplayInterface ---- */
348
349        access(all)
350        view fun getName(): String? {
351            return self.metadata["name"]
352        }
353
354        access(all)
355        view fun getDescription(): String? {
356            return self.metadata["description"]
357        }
358
359        access(all)
360        view fun getExternalURL(): MetadataViews.ExternalURL? {
361            let url = self.metadata["externalURL"]
362            return url != nil ? MetadataViews.ExternalURL(url!) : nil
363        }
364
365        access(self)
366        view fun getImage(_ key: String, _ type: String?): MetadataViews.Media? {
367            var keyName = "image:".concat(key)
368            if type != nil && (type == "svg" || type == "png" || type == "jpg") {
369                keyName = keyName.concat(":").concat(type!)
370            }
371            if self.metadata[keyName] != nil {
372                let mediaType = type == "svg"
373                    ? "image/svg+xml"
374                    : type == "png"
375                        ? "image/png"
376                        : type == "jpg"
377                            ? "image/jpeg"
378                            : "image/*"
379                return MetadataViews.Media(
380                    file: MetadataViews.HTTPFile(url: self.metadata[keyName]!),
381                    mediaType: mediaType
382                )
383            }
384            return nil
385        }
386
387        access(all)
388        view fun getBannerImage(): MetadataViews.Media? {
389            return self.getImage("banner", "svg") ?? self.getImage("banner", "png") ?? self.getImage("banner", "jpg") ?? self.getImage("banner", nil)
390        }
391
392        access(all)
393        view fun getSquareImage(): MetadataViews.Media? {
394            return self.getImage("square", "svg") ?? self.getImage("square", "png") ?? self.getImage("square", "jpg") ?? self.getImage("square", nil)
395        }
396
397        /// Get all the images
398        ///
399        access(all)
400        fun getImages(): MetadataViews.Medias {
401            let medias: [MetadataViews.Media] = []
402            if let bannerSvg = self.getImage("banner", "svg") {
403                medias.append(bannerSvg)
404            }
405            if let bannerPng = self.getImage("banner", "png") {
406                medias.append(bannerPng)
407            }
408            if let bannerJpg = self.getImage("banner", "jpg") {
409                medias.append(bannerJpg)
410            }
411            if let squareSvg = self.getImage("square", "svg") {
412                medias.append(squareSvg)
413            }
414            if let squarePng = self.getImage("square", "png") {
415                medias.append(squarePng)
416            }
417            if let squareJpg = self.getImage("square", "jpg") {
418                medias.append(squareJpg)
419            }
420            return MetadataViews.Medias(medias)
421        }
422
423        access(all)
424        view fun getSocials(): {String: MetadataViews.ExternalURL} {
425            let ret: {String: MetadataViews.ExternalURL} = {}
426            let socialKey = "social:"
427            let socialKeyLen = socialKey.length
428            for key in self.metadata.keys {
429                if key.length >= socialKeyLen && key.slice(from: 0, upTo: socialKey.length) == socialKey {
430                    let socialName = key.slice(from: socialKey.length, upTo: key.length)
431                    ret[socialName] = MetadataViews.ExternalURL(self.metadata[key]!)
432                }
433            }
434            return ret
435        }
436
437        /* --- Implement the MetadataViews.Resolver --- */
438
439        access(all)
440        view fun getViews(): [Type] {
441            return [
442                Type<MetadataViews.NFTCollectionDisplay>(),
443                Type<MetadataViews.Medias>()
444            ]
445        }
446
447        access(all)
448        fun resolveView(_ view: Type): AnyStruct? {
449            switch view {
450                case Type<MetadataViews.NFTCollectionDisplay>():
451                    return self.getCollectionDisplay()
452                case Type<MetadataViews.Medias>():
453                    return self.getImages()
454            }
455            return nil
456        }
457    }
458
459    /// Create the FT View Data
460    ///
461    access(all)
462    fun createEditableCollectionDisplay(
463        _ address: Address,
464        _ contractName: String,
465    ): @EditableNFTCollectionDisplay {
466        return <- create EditableNFTCollectionDisplay(address, contractName)
467    }
468
469    /// Build the NFT Type
470    ///
471    access(all)
472    view fun buildNFTType(_ address: Address, _ contractName: String): Type? {
473        let addrStr = address.toString()
474        let addrStrNo0x = addrStr.slice(from: 2, upTo: addrStr.length)
475        return CompositeType("A.".concat(addrStrNo0x).concat(".").concat(contractName).concat(".NFT"))
476    }
477
478    /// Build the NFT Collection Type
479    ///
480    access(all)
481    view fun buildCollectionType(_ address: Address, _ contractName: String): Type? {
482        let addrStr = address.toString()
483        let addrStrNo0x = addrStr.slice(from: 2, upTo: addrStr.length)
484        return CompositeType("A.".concat(addrStrNo0x).concat(".").concat(contractName).concat(".Collection"))
485    }
486}
487