Smart Contract

NFTList

A.15a918087ab12d86.NFTList

Valid From

86,824,244

Deployed

3d ago
Feb 24, 2026, 11:41:43 PM UTC

Dependents

15 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 basic contract of the NFT List.
7It will be used to store the list of all the Flow Standard Non-Fungible Tokens (NFTs) that are available on the Flow blockchain.
8
9*/
10import NonFungibleToken from 0x1d7e57aa55817448
11import MetadataViews from 0x1d7e57aa55817448
12import ViewResolver from 0x1d7e57aa55817448
13// TokenList Imports
14import ViewResolvers from 0x15a918087ab12d86
15import FTViewUtils from 0x15a918087ab12d86
16import NFTViewUtils from 0x15a918087ab12d86
17
18/// NFT List registry contract
19///
20access(all) contract NFTList {
21
22    /* --- Entitlement --- */
23
24    access(all) entitlement Maintainer
25    access(all) entitlement SuperAdmin
26
27
28    /* --- Events --- */
29
30    /// Event emitted when the contract is initialized
31    access(all) event ContractInitialized()
32
33    /// Event emitted when a new Non-Fungible Token is registered
34    access(all) event NFTCollectionRegistered(
35        _ address: Address,
36        _ contractName: String,
37        _ type: Type,
38    )
39    /// Event emitted when a new Non-Fungible Token is removed
40    access(all) event NFTCollectionRemoved(
41        _ address: Address,
42        _ contractName: String,
43        _ type: Type,
44    )
45    /// Event emitted when Reviewer Metadata is updated
46    access(all) event NFTListReviewerMetadataUpdated(
47        _ reviewer: Address,
48        name: String?,
49        url: String?,
50    )
51    /// Event emitted when reviewer rank is updated
52    access(all) event NFTListReviewerVerifiedUpdated(
53        _ reviewer: Address,
54        _ isVerified: Bool
55    )
56    /// Event emitted when reviewer rank is updated
57    access(all) event NFTListReviewerRankUpdated(
58        _ reviewer: Address,
59        _ rank: UInt8
60    )
61    /// Evenit emitted when an Editable FTDisplay is created
62    access(all) event NFTListReviewerEditableNFTCollectionDisplayCreated(
63        _ address: Address,
64        _ contractName: String,
65        reviewer: Address
66    )
67    /// Event emitted when a new Non-Fungible Token is reviewed
68    access(all) event NFTCollectionViewResolverUpdated(
69        _ address: Address,
70        _ contractName: String,
71        _ newResolverIdentifier: String
72    )
73    /// Event emitted when a Non-Fungible Token is evaluated
74    access(all) event NFTCollectionReviewEvaluated(
75        _ address: Address,
76        _ contractName: String,
77        _ rank: UInt8,
78        _ by: Address
79    )
80    /// Event emitted when a Non-Fungible Token is tagged
81    access(all) event NFTCollectionTagsAdded(
82        _ address: Address,
83        _ contractName: String,
84        _ tags: [String],
85        _ by: Address
86    )
87
88    /* --- Variable, Enums and Structs --- */
89
90    access(all) let registryStoragePath: StoragePath
91    access(all) let registryPublicPath: PublicPath
92    access(all) let reviewerStoragePath: StoragePath
93    access(all) let reviewerPublicPath: PublicPath
94    access(all) let maintainerStoragePath: StoragePath
95
96    /* --- Interfaces & Resources --- */
97
98    /// Interface for the FT Entry
99    ///
100    access(all) resource interface NFTEntryInterface {
101        /// Create an empty collection for the NFT
102        access(all)
103        fun createEmptyCollection(): @{NonFungibleToken.Collection}
104        // ----- View Methods -----
105        access(all)
106        view fun getIdentity(): NFTViewUtils.NFTIdentity
107        /// Get the NFT Type
108        access(all)
109        view fun getNFTType(): Type
110        /// Get the Collection Type
111        access(all)
112        view fun getCollectionType(): Type
113        /// Check if the Non-Fungible Token is reviewed by some one
114        access(all)
115        view fun isReviewedBy(_ reviewer: Address): Bool
116        /// Get the Non-Fungible Token Review
117        access(all)
118        view fun getReview(_ reviewer: Address): FTViewUtils.FTReview?
119        /// Get the display metadata of the FT, fallback to the highest rank reviewer
120        access(all)
121        fun getDisplay(_ reviewer: Address?): NFTViewUtils.NFTCollectionViewWithSource?
122        // ----- Quick Access For FTViews -----
123        /// Get the evaluation rank of the Non-Fungible Token, no fallback
124        access(all)
125        view fun getEvaluatedRank(_ reviewer: Address?): FTViewUtils.Evaluation? {
126            if let reviewerAddr = reviewer {
127                if let reviewRef = self.borrowReviewRef(reviewerAddr) {
128                    return reviewRef.getEvaluationRank()
129                }
130            }
131            return nil
132        }
133        /// Get the tags of the Fungiuble Token, fallback to the highest rank reviewer
134        access(all)
135        fun getTags(_ reviewer: Address?): [String] {
136            if let fallbackedReviewerAddr = self.tryGetReviewer(reviewer) {
137                if let reviewRef = self.borrowReviewRef(fallbackedReviewerAddr) {
138                    let returnTags = reviewRef.getTags()
139                    // Add extra tags based on the evaluation rank if the reviewer is the fallbackedReviewer
140                    if reviewer == fallbackedReviewerAddr {
141                        if reviewRef.evalRank.rawValue == FTViewUtils.Evaluation.BLOCKED.rawValue {
142                            returnTags.insert(at: 0, "Blocked")
143                        } else if reviewRef.evalRank.rawValue == FTViewUtils.Evaluation.FEATURED.rawValue {
144                            returnTags.insert(at: 0, "Featured")
145                            returnTags.insert(at: 0, "Verified")
146                        } else if reviewRef.evalRank.rawValue == FTViewUtils.Evaluation.VERIFIED.rawValue {
147                            returnTags.insert(at: 0, "Verified")
148                        } else if reviewRef.evalRank.rawValue == FTViewUtils.Evaluation.PENDING.rawValue {
149                            returnTags.insert(at: 0, "Pending")
150                        }
151                    }
152                    return returnTags
153                }
154            }
155            return []
156        }
157        // ----- Internal Methods: Used by Reviewer -----
158        /// Try to get a reviewer address
159        access(contract)
160        view fun tryGetReviewer(_ reviewer: Address?): Address? {
161            var reviewerAddr = reviewer
162            if reviewerAddr == nil {
163                let registry = NFTList.borrowRegistry()
164                reviewerAddr = registry.getHighestRankVerifiedCustomizedReviewer(self.getNFTType())
165            }
166            return reviewerAddr
167        }
168        access(contract)
169        view fun borrowReviewRef(_ reviewer: Address): &FTViewUtils.FTReview?
170        /// Add a new review to the FT
171        access(contract)
172        fun addReview(_ reviewer: Address, _ review: FTViewUtils.FTReview)
173    }
174
175    /// Resource for the Non-Fungible Token Entry
176    ///
177    access(all) resource NFTCollectionEntry: NFTEntryInterface {
178        access(all)
179        let identity: NFTViewUtils.NFTIdentity
180        // Reviewer => FTReview
181        access(self)
182        let reviewers: {Address: FTViewUtils.FTReview}
183        // view resolver
184        access(all)
185        var viewResolver: @{ViewResolver.Resolver}
186
187        init(
188            _ address: Address,
189            _ contractName: String,
190        ) {
191            self.reviewers = {}
192            self.identity = NFTViewUtils.NFTIdentity(address, contractName)
193            self.viewResolver <- ViewResolvers.createContractViewResolver(address: address, contractName: contractName)
194            // ensure display and vault exists
195            self.getDisplay(nil)
196            self.getCollectionData()
197        }
198
199        // ----- Implementing the FTMetadataInterface -----
200
201        /// Create an empty vault for the FT
202        ///
203        access(all)
204        fun createEmptyCollection(): @{NonFungibleToken.Collection} {
205            let contractRef = self.borrowNFTContract()
206            let nftType = self.identity.buildNFTType()
207            return <- contractRef.createEmptyCollection(nftType: nftType)
208        }
209
210        /// Get the Non-Fungible Token Identity
211        ///
212        access(all)
213        view fun getIdentity(): NFTViewUtils.NFTIdentity {
214            return self.identity
215        }
216
217        /// Check if the Non-Fungible Token is reviewed by some one
218        ///
219        access(all)
220        view fun isReviewedBy(_ reviewer: Address): Bool {
221            return self.reviewers[reviewer] != nil
222        }
223
224        /// Get the Non-Fungible Token Review
225        ///
226        access(all)
227        view fun getReview(_ reviewer: Address): FTViewUtils.FTReview? {
228            return self.reviewers[reviewer]
229        }
230
231        /// Get the display metadata of the FT
232        ///
233        access(all)
234        fun getDisplay(_ reviewer: Address?): NFTViewUtils.NFTCollectionViewWithSource? {
235            var source: Address? = nil
236
237            let viewResolver = self.borrowViewResolver()
238            var retNFTDisplay = MetadataViews.getNFTCollectionDisplay(viewResolver)
239
240            var reviewerAddr = self.tryGetReviewer(reviewer)
241            if let addr = reviewerAddr {
242                if let reviewerRef = NFTList.borrowReviewerPublic(addr) {
243                    if let displayRef = reviewerRef.borrowNFTCollectionDisplayReader(self.identity.address, self.identity.contractName) {
244                        let socials = retNFTDisplay?.socials ?? {}
245                        let extraSocials = displayRef.getSocials()
246                        for key in extraSocials.keys {
247                            socials[key] = extraSocials[key]
248                        }
249                        source = addr
250                        retNFTDisplay = MetadataViews.NFTCollectionDisplay(
251                            name: displayRef.getName() ?? retNFTDisplay?.name ?? "Unkonwn",
252                            description: displayRef.getDescription() ?? retNFTDisplay?.description ?? "No Description",
253                            externalURL: displayRef.getExternalURL() ?? retNFTDisplay?.externalURL ?? MetadataViews.ExternalURL("https://fixes.world"),
254                            squareImage: displayRef.getSquareImage() ?? retNFTDisplay?.squareImage ?? displayRef.getDefaultSquareImage(),
255                            bannerImage: displayRef.getBannerImage() ?? retNFTDisplay?.bannerImage ?? displayRef.getDefaultBannerImage(),
256                            socials: socials
257                        )
258                    }
259                }
260            }
261            return retNFTDisplay != nil
262                ? NFTViewUtils.NFTCollectionViewWithSource(source, retNFTDisplay!)
263                : nil
264        }
265
266        /// Get the vault data of the FT
267        ///
268        access(all)
269        fun getCollectionData(): MetadataViews.NFTCollectionData {
270            return MetadataViews.getNFTCollectionData(self.borrowViewResolver())
271                ?? panic("Failed to load the NFT Collection Data for ".concat(self.identity.toString()))
272        }
273
274        /// Get the FT Type
275        access(all)
276        view fun getNFTType(): Type {
277            return self.identity.buildNFTType()
278        }
279        /// Get the Collection Type
280        access(all)
281        view fun getCollectionType(): Type {
282            return self.identity.buildCollectionType()
283        }
284
285        // ----- Internal Methods -----
286
287        /// Add a new review to the FT
288        ///
289        access(contract)
290        fun addReview(_ reviewer: Address, _ review: FTViewUtils.FTReview) {
291            pre {
292                self.reviewers[reviewer] == nil:
293                    "Reviewer already exists"
294            }
295            self.reviewers[reviewer] = review
296        }
297
298        /// Borrow the review reference
299        ///
300        access(contract)
301        view fun borrowReviewRef(_ reviewer: Address): &FTViewUtils.FTReview? {
302            return &self.reviewers[reviewer]
303        }
304
305        /// Borrow the View Resolver
306        ///
307        access(contract)
308        view fun borrowViewResolver(): &{ViewResolver.Resolver} {
309            return &self.viewResolver
310        }
311
312        /// Borrow the Non-Fungible Token Contract
313        ///
314        access(contract)
315        fun borrowNFTContract(): &{NonFungibleToken} {
316            return self.identity.borrowNFTContract()
317        }
318    }
319
320    /// Interface for the Non-Fungible Token Reviewer
321    ///
322    access(all) resource interface NFTListReviewerInterface {
323        access(all)
324        view fun getAddress(): Address {
325            return self.owner?.address ?? panic("Owner not found")
326        }
327        access(all)
328        view fun getName(): String? {
329            let metadata = self.getMetadata()
330            return metadata["name"]
331        }
332        access(all)
333        view fun getUrl(): String? {
334            let metadata = self.getMetadata()
335            return metadata["url"]
336        }
337        access(all)
338        view fun getMetadata(): {String: String}
339        access(all)
340        view fun getReviewedNFTAmount(): Int
341        access(all)
342        view fun getCustomizedNFTAmount(): Int
343        access(all)
344        view fun getReviewedNFTTypes(): [Type]
345        access(all)
346        view fun getFeaturedNFTTypes(): [Type]
347        access(all)
348        view fun getVerifiedNFTTypes(): [Type]
349        access(all)
350        view fun getBlockedNFTTypes(): [Type]
351        access(all)
352        view fun borrowNFTCollectionDisplayReaderByNftType(_ nftType: Type): &NFTViewUtils.EditableNFTCollectionDisplay?
353        access(all)
354        view fun borrowNFTCollectionDisplayReader(
355            _ address: Address,
356            _ contractName: String,
357        ): &NFTViewUtils.EditableNFTCollectionDisplay?
358    }
359
360    /// Maintainer interface for the Non-Fungible Token Reviewer
361    ///
362    access(all) resource interface NFTListReviewMaintainer {
363        /// Update Reviewer Metadata
364        access(Maintainer)
365        fun updateMetadata(name: String?, url: String?)
366        /// Review the Non-Fungible Token with Evaluation
367        access(Maintainer)
368        fun reviewNFTEvalute(
369            _ address: Address,
370            _ contractName: String,
371            rank: FTViewUtils.Evaluation
372        )
373        /// Review the Non-Fungible Token, add tags
374        access(Maintainer)
375        fun reviewNFTAddTags(
376            _ address: Address,
377            _ contractName: String,
378            tags: [String]
379        )
380        /// Register the Non-Fungible Token Display Patch
381        access(Maintainer)
382        fun registerNFTCollectionDisplayPatch(
383            _ address: Address,
384            _ contractName: String,
385        )
386        /// Borrow or create the FTDisplay editor
387        access(Maintainer)
388        view fun borrowNFTCollectionDisplayEditor(
389            _ address: Address,
390            _ contractName: String,
391        ): auth(NFTViewUtils.Editable) &NFTViewUtils.EditableNFTCollectionDisplay?
392    }
393
394    /// The resource for the FT Reviewer
395    ///
396    access(all) resource NFTListReviewer: NFTListReviewMaintainer, NFTListReviewerInterface {
397        access(self)
398        let metadata: {String: String}
399        access(self)
400        let storedDisplayPatches: @{Type: NFTViewUtils.EditableNFTCollectionDisplay}
401        access(self)
402        let reviewed: {Type: FTViewUtils.Evaluation}
403
404        init() {
405            self.metadata = {}
406            self.reviewed = {}
407            self.storedDisplayPatches <- {}
408        }
409
410        // --- Implement the FungibleTokenReviewMaintainer ---
411
412        /// Update Reviewer Metadata
413        ///
414        access(Maintainer)
415        fun updateMetadata(name: String?, url: String?) {
416            if name != nil {
417                self.metadata["name"] = name!
418            }
419
420            if url != nil {
421                self.metadata["url"] = url!
422            }
423
424            // emit the event
425            emit NFTListReviewerMetadataUpdated(
426                self.getAddress(),
427                name: name,
428                url: url,
429            )
430        }
431
432        /// Review the Non-Fungible Token with Evaluation
433        ///
434        access(Maintainer)
435        fun reviewNFTEvalute(
436            _ address: Address,
437            _ contractName: String,
438            rank: FTViewUtils.Evaluation
439        ) {
440            let registery = NFTList.borrowRegistry()
441            let entryRef = registery.borrowNFTEntryByContract(address, contractName)
442                ?? panic("Failed to load the Non-Fungible Token Entry")
443
444            let reviewerAddr = self.getAddress()
445            var isUpdated = false
446            if let reviewRef = entryRef.borrowReviewRef(reviewerAddr) {
447                if reviewRef.evalRank.rawValue != rank.rawValue {
448                    reviewRef.updateEvaluationRank(rank)
449                    isUpdated = true
450                }
451            } else {
452                entryRef.addReview(reviewerAddr, FTViewUtils.FTReview(rank))
453                isUpdated = true
454            }
455
456            // If not updated, then return
457            if !isUpdated {
458                return
459            }
460
461            let identity = entryRef.getIdentity()
462            let type = identity.buildNFTType()
463
464            // update reviewed status locally
465            self.reviewed[type] = rank
466
467            // emit the event
468            emit NFTCollectionReviewEvaluated(
469                identity.address,
470                identity.contractName,
471                rank.rawValue,
472                reviewerAddr
473            )
474        }
475
476        /// Review the Non-Fungible Token, add tags
477        ///
478        access(Maintainer)
479        fun reviewNFTAddTags(
480            _ address: Address,
481            _ contractName: String,
482            tags: [String]
483        ) {
484            pre {
485                tags.length > 0: "Tags should not be empty"
486            }
487
488            let registery = NFTList.borrowRegistry()
489            let entryRef = registery.borrowNFTEntryByContract(address, contractName)
490                ?? panic("Failed to load the Non-Fungible Token Entry")
491
492            let reviewerAddr = self.getAddress()
493            if entryRef.borrowReviewRef(reviewerAddr) == nil {
494                // add the review with UNVERIFIED evaluation
495                self.reviewNFTEvalute(address, contractName, rank: FTViewUtils.Evaluation.UNVERIFIED)
496            }
497            let ref = entryRef.borrowReviewRef(reviewerAddr)
498                ?? panic("Failed to load the Non-Fungible Token Review")
499            var isUpdated = false
500            for tag in tags {
501                // ingore the eval tags
502                if tag == "Verified" || tag == "Featured" || tag == "Pending" || tag == "Blocked" {
503                    continue
504                }
505                if ref.addTag(tag) {
506                    isUpdated = true
507                }
508            }
509
510            // If not updated, then return
511            if !isUpdated {
512                return
513            }
514
515            let identity = entryRef.getIdentity()
516            // emit the event
517            emit NFTCollectionTagsAdded(
518                identity.address,
519                identity.contractName,
520                tags,
521                reviewerAddr
522            )
523        }
524
525        /// Register the Non-Fungible Token Display Patch
526        ///
527        access(Maintainer)
528        fun registerNFTCollectionDisplayPatch(
529            _ address: Address,
530            _ contractName: String
531        ) {
532            let registery = NFTList.borrowRegistry()
533
534            let entry = registery.borrowNFTEntryByContract(address, contractName)
535                ?? panic("Token not registered")
536            let nftType = entry.getNFTType()
537            assert(
538                self.storedDisplayPatches[nftType] == nil,
539                message: "Editable FTDisplay already exists"
540            )
541
542            // create a Editable FTDisplay resource it the reviewer storage
543            let editableFTDisplay <- NFTViewUtils.createEditableCollectionDisplay(address, contractName)
544            let ftDisplayId = editableFTDisplay.uuid
545            // save in the resource
546            self.storedDisplayPatches[nftType] <-! editableFTDisplay
547
548            registery.onReviewerNFTCollectionCustomized(self.getAddress(), nftType)
549
550            // emit the event for editable FTDisplay
551            emit NFTListReviewerEditableNFTCollectionDisplayCreated(
552                address,
553                contractName,
554                reviewer: self.getAddress()
555            )
556        }
557
558        /// Borrow or create the editor
559        ///
560        access(Maintainer)
561        view fun borrowNFTCollectionDisplayEditor(
562            _ address: Address,
563            _ contractName: String,
564        ): auth(NFTViewUtils.Editable) &NFTViewUtils.EditableNFTCollectionDisplay? {
565            let nftType = NFTViewUtils.buildNFTType(address, contractName)
566                ?? panic("Failed to build the NFT Type")
567            return &self.storedDisplayPatches[nftType]
568        }
569
570        // --- Implement the FungibleTokenReviewerInterface ---
571
572        access(all)
573        view fun getMetadata(): {String: String} {
574            return self.metadata
575        }
576
577        /// Return the amount of Non-Fungible Token which reviewed by the reviewer
578        ///
579        access(all)
580        view fun getReviewedNFTAmount(): Int {
581            return self.reviewed.keys.length
582        }
583
584        /// Return the amount of Non-Fungible Token which display customized by the reviewer
585        ///
586        access(all)
587        view fun getCustomizedNFTAmount(): Int {
588            return self.storedDisplayPatches.keys.length
589        }
590
591        /// Return all Non-Fungible Token Types reviewed by the reviewer
592        ///
593        access(all)
594        view fun getReviewedNFTTypes(): [Type] {
595            return self.reviewed.keys
596        }
597
598        /// Return all Non-Fungible Token Types with FEATURED evaluation
599        ///
600        access(all)
601        view fun getFeaturedNFTTypes(): [Type] {
602            let reviewedRef = &self.reviewed as &{Type: FTViewUtils.Evaluation}
603            return self.reviewed.keys.filter(view fun(type: Type): Bool {
604                return reviewedRef[type]?.rawValue == FTViewUtils.Evaluation.FEATURED.rawValue
605            })
606        }
607
608        /// Return all Non-Fungible Token Types with VERIFIED or FEATURED evaluation
609        ///
610        access(all)
611        view fun getVerifiedNFTTypes(): [Type] {
612            let reviewedRef = &self.reviewed as &{Type: FTViewUtils.Evaluation}
613            return self.reviewed.keys.filter(view fun(type: Type): Bool {
614                return reviewedRef[type]?.rawValue == FTViewUtils.Evaluation.VERIFIED.rawValue
615                    || reviewedRef[type]?.rawValue == FTViewUtils.Evaluation.FEATURED.rawValue
616            })
617        }
618
619        /// Return all Non-Fungible Token Types with BLOCKED evaluation
620        ///
621        access(all)
622        view fun getBlockedNFTTypes(): [Type] {
623            let reviewedRef = &self.reviewed as &{Type: FTViewUtils.Evaluation}
624            return self.reviewed.keys.filter(view fun(type: Type): Bool {
625                return reviewedRef[type]?.rawValue == FTViewUtils.Evaluation.BLOCKED.rawValue
626            })
627        }
628
629        /// Borrow the FTDisplay editor
630        ///
631        access(all)
632        view fun borrowNFTCollectionDisplayReader(
633            _ address: Address,
634            _ contractName: String,
635        ): &NFTViewUtils.EditableNFTCollectionDisplay? {
636            return self.borrowNFTCollectionDisplayEditor(address, contractName)
637        }
638
639        /// Borrow the FTDisplay editor
640        ///
641        access(all)
642        view fun borrowNFTCollectionDisplayReaderByNftType(_ nftType: Type): &NFTViewUtils.EditableNFTCollectionDisplay? {
643            return &self.storedDisplayPatches[nftType]
644        }
645
646        // --- Internal Methods ---
647    }
648
649    /// The Maintainer for the Non-Fungible Token Reviewer
650    ///
651    access(all) resource ReviewMaintainer: NFTListReviewMaintainer {
652        access(self)
653        let reviewerCap: Capability<auth(Maintainer) &NFTListReviewer>
654
655        init(
656            _ cap: Capability<auth(Maintainer) &NFTListReviewer>
657        ) {
658            pre {
659                cap.check(): "Invalid capability"
660            }
661            self.reviewerCap = cap
662        }
663
664        /// Get the reviewer address
665        ///
666        access(all)
667        view fun getReviewerAddress(): Address {
668            return self.reviewerCap.address
669        }
670
671        /// Update Reviewer Metadata
672        ///
673        access(Maintainer)
674        fun updateMetadata(name: String?, url: String?) {
675            self._borrowReviewer().updateMetadata(name: name, url: url)
676        }
677
678        /// Review the Non-Fungible Token with Evaluation
679        ///
680        access(Maintainer)
681        fun reviewNFTEvalute(_ address: Address, _ contractName: String, rank: FTViewUtils.Evaluation) {
682            self._borrowReviewer().reviewNFTEvalute(address, contractName, rank: rank)
683        }
684
685        /// Review the Non-Fungible Token, add tags
686        ///
687        access(Maintainer)
688        fun reviewNFTAddTags(_ address: Address, _ contractName: String, tags: [String]) {
689            self._borrowReviewer().reviewNFTAddTags(address, contractName, tags: tags)
690        }
691
692        /// Register the Non-Fungible Token Display Patch
693        access(Maintainer)
694        fun registerNFTCollectionDisplayPatch(
695            _ address: Address,
696            _ contractName: String
697        ) {
698            self._borrowReviewer().registerNFTCollectionDisplayPatch(address, contractName)
699        }
700
701        /// Borrow or create the FTDisplay editor
702        access(Maintainer)
703        view fun borrowNFTCollectionDisplayEditor(
704            _ address: Address,
705            _ contractName: String,
706        ): auth(NFTViewUtils.Editable) &NFTViewUtils.EditableNFTCollectionDisplay? {
707            return self._borrowReviewer().borrowNFTCollectionDisplayEditor(address, contractName)
708        }
709
710        /* ---- Internal Methods ---- */
711
712        access(self)
713        view fun _borrowReviewer(): auth(Maintainer) &NFTListReviewer {
714            return self.reviewerCap.borrow() ?? panic("Failed to borrow the reviewer")
715        }
716    }
717
718    /// Interface for the Token List Viewer
719    ///
720    access(all) resource interface NFTListViewer {
721        // --- Read Methods ---
722        /// Return all available reviewers
723        access(all)
724        view fun getReviewers(): [Address]
725        /// Get the reviewer rank
726        access(all)
727        view fun getReviewerRank(_ reviewer: Address): ReviewerRank?
728        /// Return all verified reviewers
729        access(all)
730        view fun getVerifiedReviewers(): [Address]
731        /// Return if the reviewer is verified
732        access(all)
733        view fun isReviewerVerified(_ reviewer: Address): Bool
734        /// Get the amount of Non-Fungible Token Entries
735        access(all)
736        view fun getNFTEntriesAmount(): Int
737        /// Get all the Non-Fungible Token Entries
738        access(all)
739        view fun getAllNFTEntries(): [Type]
740        /// Get the Non-Fungible Token Entry by the page and size
741        access(all)
742        view fun getNFTEntries(_ page: Int, _ size: Int): [Type]
743        /// Get the Non-Fungible Token Entry by the address
744        access(all)
745        view fun getNFTEntriesByAddress(_ address: Address): [Type]
746        /// Get the customized reviewers
747        access(all)
748        view fun getHighestRankVerifiedCustomizedReviewer(_ nftType: Type): Address?
749        /// Borrow the NFT Entry by the type
750        access(all)
751        view fun borrowNFTEntry(_ nftType: Type): &NFTCollectionEntry?
752        /// Borrow the NFT Entry by the contract address
753        access(all)
754        view fun borrowNFTEntryByContract(_ address: Address, _ contractName: String): &NFTCollectionEntry?
755        // --- Write Methods ---
756        /// Register a new standard Non-Fungible Token Entry to the registry
757        access(contract)
758        fun registerStandardNonFungibleToken(_ address: Address, _ contractName: String)
759        /// Invoked when a new Non-Fungible Token is customized by the reviewer
760        access(contract)
761        fun onReviewerNFTCollectionCustomized(_ reviewer: Address, _ nftType: Type)
762    }
763
764    access(all) enum ReviewerRank: UInt8 {
765        access(all) case NORMAL
766        access(all) case ADVANCED
767        access(all) case EXPERT
768    }
769
770    /// The Token List Registry
771    ///
772    access(all) resource Registry: NFTListViewer {
773        // Address => isVerified
774        access(self)
775        let verifiedReviewers: {Address: Bool}
776        access(self)
777        let reviewerRanks: {Address: ReviewerRank}
778        // NFT Type => NFT Collection Entry
779        access(self)
780        let entries: @{Type: NFTCollectionEntry}
781        // Entry ID => NFT Type
782        access(self)
783        let entriesIdMapping: {UInt64: Type}
784        // Address => [contractName]
785        access(self)
786        let addressMapping: {Address: [String]}
787        // Customized NFT Views
788        access(self)
789        let customizedNFTViews: {Type: [Address]}
790
791        init() {
792            self.verifiedReviewers = {}
793            self.reviewerRanks = {}
794            self.entriesIdMapping = {}
795            self.entries <- {}
796            self.addressMapping = {}
797            self.customizedNFTViews = {}
798        }
799
800        // ----- Read Methods -----
801
802        /// Return all available reviewers
803        ///
804        access(all)
805        view fun getReviewers(): [Address] {
806            return self.reviewerRanks.keys
807        }
808
809        /// Get the reviewer rank
810        ///
811        access(all)
812        view fun getReviewerRank(_ reviewer: Address): ReviewerRank? {
813            return self.reviewerRanks[reviewer]
814        }
815
816        /// Return all verified reviewers
817        ///
818        access(all)
819        view fun getVerifiedReviewers(): [Address] {
820            return self.verifiedReviewers.keys
821        }
822
823        /// Return if the reviewer is verified
824        ///
825        access(all)
826        view fun isReviewerVerified(_ reviewer: Address): Bool {
827            return self.verifiedReviewers[reviewer] ?? false
828        }
829
830        /// Get the amount of Non-Fungible Token Entries
831        access(all)
832        view fun getNFTEntriesAmount(): Int {
833            return self.entries.keys.length
834        }
835
836        /// Get all the Non-Fungible Token Entries
837        ///
838        access(all)
839        view fun getAllNFTEntries(): [Type] {
840            return self.entries.keys
841        }
842
843        /// Get the Non-Fungible Token Entry by the page and size
844        ///
845        access(all)
846        view fun getNFTEntries(_ page: Int, _ size: Int): [Type] {
847            pre {
848                page >= 0: "Invalid page"
849                size > 0: "Invalid size"
850            }
851            let max = self.getNFTEntriesAmount()
852            let start = page * size
853            if start > max {
854                return []
855            }
856            var end = start + size
857            if end > max {
858                end = max
859            }
860            return self.entries.keys.slice(from: start, upTo: end)
861        }
862
863        /// Get the Non-Fungible Token Entry by the address
864        ///
865        access(all)
866        view fun getNFTEntriesByAddress(_ address: Address): [Type] {
867            if let contracts = self.borrowAddressContractsRef(address) {
868                var types: [Type] = []
869                for contractName in contracts {
870                    if let type = NFTViewUtils.buildNFTType(address, contractName) {
871                        types = types.concat([type])
872                    }
873                }
874                return types
875            }
876            return []
877        }
878
879        /// Get the highest rank customized reviewers
880        ///
881        access(all)
882        view fun getHighestRankVerifiedCustomizedReviewer(_ nftType: Type): Address? {
883            if let reviewers = self.customizedNFTViews[nftType] {
884                var highestRank: ReviewerRank? = nil
885                var highestReviewer: Address? = nil
886                for reviewer in reviewers {
887                    if self.verifiedReviewers[reviewer] != true {
888                        continue
889                    }
890                    if let rank = self.reviewerRanks[reviewer] {
891                        if highestRank == nil || rank.rawValue > highestRank!.rawValue {
892                            highestRank = rank
893                            highestReviewer = reviewer
894                            // break if the reviewer is verified
895                            if self.verifiedReviewers[reviewer] == true {
896                                break
897                            }
898                        }
899                    }
900                }
901                return highestReviewer
902            }
903            return nil
904        }
905
906        /// Borrow the NFT Entry by the type
907        access(all)
908        view fun borrowNFTEntry(_ nftType: Type): &NFTCollectionEntry? {
909            return &self.entries[nftType]
910        }
911
912        /// Borrow the NFT Entry by the contract address
913        access(all)
914        view fun borrowNFTEntryByContract(_ address: Address, _ contractName: String): &NFTCollectionEntry? {
915            var entry: &NFTCollectionEntry? = nil
916            if let collectionType = NFTViewUtils.buildCollectionType(address, contractName) {
917                entry = self.borrowNFTEntry(collectionType)
918            }
919            if entry == nil {
920                if let nftType = NFTViewUtils.buildNFTType(address, contractName) {
921                    entry = self.borrowNFTEntry(nftType)
922                }
923            }
924            return entry
925        }
926
927        // ----- Write Methods -----
928
929        /// Update the reviewer verified status
930        ///
931        access(SuperAdmin)
932        fun updateReviewerVerified(_ reviewer: Address, _ verified: Bool) {
933            pre {
934                NFTList.borrowReviewerPublic(reviewer) != nil: "FT Reviewer not found"
935            }
936            self.verifiedReviewers[reviewer] = verified
937
938            // emit the event
939            emit NFTListReviewerVerifiedUpdated(
940                reviewer,
941                verified
942            )
943        }
944
945        /// Remove a Non-Fungible Token Entry from the registry
946        ///
947        access(SuperAdmin)
948        fun removeNFTCollection(_ type: Type): Bool {
949            return self._removeNFTCollection(type)
950        }
951
952        /// Register a new standard Non-Fungible Token Entry to the registry
953        ///
954        access(contract)
955        fun registerStandardNonFungibleToken(_ address: Address, _ contractName: String) {
956            pre {
957                NFTList.isNFTCollectionRegistered(address, contractName) == false: "Fungible Token already registered"
958            }
959            self._register(<- create NFTCollectionEntry(address, contractName))
960        }
961
962        /// Invoked when a new Non-Fungible Token is customized by the reviewer
963        ///
964        access(contract)
965        fun onReviewerNFTCollectionCustomized(_ reviewer: Address, _ nftType: Type) {
966            pre {
967                NFTList.borrowReviewerPublic(reviewer) != nil: "FT Reviewer not found"
968            }
969            // ensure the tokenType exists
970            if self.customizedNFTViews[nftType] == nil {
971                self.customizedNFTViews[nftType] = []
972            }
973            // add the reviewer to the list
974            if let arrRef = &self.customizedNFTViews[nftType] as &[Address]? {
975                if arrRef.firstIndex(of: reviewer) == nil {
976                    self.customizedNFTViews[nftType]?.append(reviewer)
977                }
978            }
979            // update the rank
980            self._updateReviewerRank(reviewer)
981        }
982
983        // ----- Internal Methods -----
984
985        access(self)
986        fun _updateReviewerRank(_ reviewer: Address) {
987            let reviewerRef = NFTList.borrowReviewerPublic(reviewer)
988                ?? panic("Could not find the FT Reviewer")
989            let oldRank = self.reviewerRanks[reviewer]
990            var newRank: NFTList.ReviewerRank? = nil
991            // ensure the reviewer rank exists
992            if self.reviewerRanks[reviewer] == nil {
993                newRank = ReviewerRank.NORMAL
994            } else {
995                // update the rank by the amount of customized FT
996                let reviewedAmt = reviewerRef.getReviewedNFTAmount()
997                let customizedAmt = reviewerRef.getCustomizedNFTAmount()
998                let weight = customizedAmt * 5 + reviewedAmt
999                if weight >= 1000 {
1000                    newRank = ReviewerRank.EXPERT
1001                } else if weight >= 200 {
1002                    newRank = ReviewerRank.ADVANCED
1003                }
1004            }
1005
1006            if newRank != nil && oldRank != newRank {
1007                self.reviewerRanks[reviewer] = newRank!
1008                // emit the event
1009                emit NFTListReviewerRankUpdated(
1010                    reviewer,
1011                    newRank!.rawValue
1012                )
1013            }
1014        }
1015
1016        /// Remove a Non-Fungible Token Entry from the registry
1017        ///
1018        access(self)
1019        fun _removeNFTCollection(_ nftType: Type): Bool {
1020            if self.entries[nftType] == nil {
1021                return false
1022            }
1023            let entry <- self.entries.remove(key: nftType)
1024                ?? panic("Could not remove the Non-Fungible Token Entry")
1025            self.entriesIdMapping.remove(key: entry.uuid)
1026
1027            let identity = entry.getIdentity()
1028            if let addrRef = self.borrowAddressContractsRef(identity.address) {
1029                if let idx = addrRef.firstIndex(of: identity.contractName) {
1030                    addrRef.remove(at: idx)
1031                }
1032            }
1033            destroy entry
1034
1035            // emit the event
1036            emit NFTCollectionRemoved(
1037                identity.address,
1038                identity.contractName,
1039                nftType
1040            )
1041            return true
1042        }
1043
1044        /// Add a new Non-Fungible Token Entry to the registry
1045        ///
1046        access(self)
1047        fun _register(_ entry: @NFTCollectionEntry) {
1048            pre {
1049                self.entries[entry.getNFTType()] == nil:
1050                    "FungibleToken Entry already exists in the registry"
1051                self.entriesIdMapping[entry.uuid] == nil:
1052                    "FungibleToken Entry ID already exists in the registry"
1053            }
1054            let nftType = entry.getNFTType()
1055            self.entries[nftType] <-! entry
1056
1057            let ref = &self.entries[nftType] as &NFTCollectionEntry?
1058                ?? panic("Could not borrow the FT Entry")
1059            // Add the ID mapping
1060            self.entriesIdMapping[ref.uuid] = nftType
1061            // Add the address mapping
1062            if let addrRef = self.borrowAddressContractsRef(ref.identity.address) {
1063                addrRef.append(ref.identity.contractName)
1064            } else {
1065                self.addressMapping[ref.identity.address] = [ref.identity.contractName]
1066            }
1067
1068            // emit the event
1069            emit NFTCollectionRegistered(
1070                ref.identity.address,
1071                ref.identity.contractName,
1072                nftType
1073            )
1074        }
1075
1076        access(self)
1077        view fun borrowAddressContractsRef(_ addr: Address): auth(Mutate) &[String]? {
1078            return &self.addressMapping[addr]
1079        }
1080    }
1081
1082    /* --- Public Methods --- */
1083
1084    /// Create a new NFT List Reviewer
1085    ///
1086    access(all)
1087    fun createNFTListReviewer(): @NFTListReviewer {
1088        return <- create NFTListReviewer()
1089    }
1090
1091    /// Create a new Non-Fungible Token Review Maintainer
1092    ///
1093    access(all)
1094    fun createNFTListReviewMaintainer(
1095        _ cap: Capability<auth(Maintainer) &NFTListReviewer>
1096    ): @ReviewMaintainer {
1097        return <- create ReviewMaintainer(cap)
1098    }
1099
1100    /// Get the Non-Fungible Token Reviewer capability
1101    ///
1102    access(all)
1103    view fun getReviewerCapability(_ addr: Address): Capability<&NFTListReviewer> {
1104        return getAccount(addr).capabilities
1105            .get<&NFTListReviewer>(self.reviewerPublicPath)
1106    }
1107
1108    /// Borrow the public capability of the Non-Fungible Token Reviewer
1109    ///
1110    access(all)
1111    view fun borrowReviewerPublic(_ addr: Address): &NFTListReviewer? {
1112        return self.getReviewerCapability(addr).borrow()
1113    }
1114
1115    /// Borrow the public capability of  Token List Registry
1116    ///
1117    access(all)
1118    view fun borrowRegistry(): &{NFTListViewer} {
1119        return getAccount(self.account.address)
1120            .capabilities.get<&{NFTListViewer}>(self.registryPublicPath)
1121            .borrow()
1122            ?? panic("Could not borrow the Registry reference")
1123    }
1124
1125    /// Check if the NFT is registered
1126    ///
1127    access(all)
1128    view fun isNFTCollectionRegistered(_ address: Address, _ contractName: String): Bool {
1129        let registry: &{NFTList.NFTListViewer} = self.borrowRegistry()
1130        let entry = registry.borrowNFTEntryByContract(address, contractName)
1131        return entry != nil
1132    }
1133
1134    /// Check if the NFT is valid to register
1135    access(all)
1136    fun isValidToRegister(_ address: Address, _ contractName: String): Bool {
1137        if let contractRef = ViewResolvers.borrowContractViewResolver(address, contractName) {
1138            let colDisplayType = Type<MetadataViews.NFTCollectionDisplay>()
1139            let colDataType = Type<MetadataViews.NFTCollectionData>()
1140            let contractViews = contractRef.getContractViews(resourceType: nil)
1141            if contractViews.contains(colDataType) == false || contractViews.contains(colDisplayType) == false {
1142                return false
1143            }
1144            var convertedDisplayView: MetadataViews.NFTCollectionDisplay? = nil
1145            var convertedDataView: MetadataViews.NFTCollectionData? = nil
1146            if let displayView = contractRef.resolveContractView(resourceType: nil, viewType: colDisplayType) {
1147                convertedDisplayView = displayView as? MetadataViews.NFTCollectionDisplay
1148            } else {
1149                return false
1150            }
1151            if let dataView = contractRef.resolveContractView(resourceType: nil, viewType: colDataType) {
1152                convertedDataView = dataView as? MetadataViews.NFTCollectionData
1153            } else {
1154                return false
1155            }
1156            if convertedDisplayView != nil && convertedDataView != nil {
1157                return true
1158            }
1159        }
1160        return false
1161    }
1162
1163    /// Try to register a new NFT, if already registered, then do nothing
1164    ///
1165    access(all)
1166    fun ensureNFTCollectionRegistered(_ address: Address, _ contractName: String) {
1167        if !self.isNFTCollectionRegistered(address, contractName) && self.isValidToRegister(address, contractName) {
1168            let registry = self.borrowRegistry()
1169            registry.registerStandardNonFungibleToken(address, contractName)
1170        }
1171    }
1172
1173    /// The prefix for the paths
1174    ///
1175    access(all)
1176    view fun getPathPrefix(): String {
1177        return "NFTList_".concat(self.account.address.toString()).concat("_")
1178    }
1179
1180    /// Generate the review maintainer capability ID
1181    ///
1182    access(all)
1183    view fun generateReviewMaintainerCapabilityId(_ addr: Address): String {
1184        return NFTList.getPathPrefix().concat("PrivateIdentity_ReviewMaintainer_".concat(addr.toString()))
1185    }
1186
1187    init() {
1188        // Identifiers
1189        let identifier = NFTList.getPathPrefix()
1190        self.registryStoragePath = StoragePath(identifier: identifier.concat("_Registry"))!
1191        self.registryPublicPath = PublicPath(identifier: identifier.concat("_Registry"))!
1192
1193        self.reviewerStoragePath = StoragePath(identifier: identifier.concat("_Reviewer"))!
1194        self.reviewerPublicPath = PublicPath(identifier: identifier.concat("_Reviewer"))!
1195
1196        self.maintainerStoragePath = StoragePath(identifier: identifier.concat("_Maintainer"))!
1197
1198        // Create the Token List Registry
1199        let registry <- create Registry()
1200        self.account.storage.save(<- registry, to: self.registryStoragePath)
1201        // link the public capability
1202        let cap = self.account.capabilities
1203            .storage.issue<&{NFTListViewer}>(self.registryStoragePath)
1204        self.account.capabilities.publish(cap, at: self.registryPublicPath)
1205
1206        // Emit the initialized event
1207        emit ContractInitialized()
1208    }
1209}
1210