Smart Contract

TokenList

A.15a918087ab12d86.TokenList

Valid From

86,824,223

Deployed

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

Dependents

14 imports
1/**
2> Author: Fixes Lab <https://github.com/fixes-world/>
3
4# Token List - A on-chain list of Flow Standard Fungible Tokens (FTs).
5
6This is the basic contract of the Token List.
7It will be used to store the list of all the Flow Standard Fungible Tokens (FTs) that are available on the Flow blockchain.
8
9*/
10import FungibleToken from 0xf233dcee88fe0abe
11import MetadataViews from 0x1d7e57aa55817448
12import ViewResolver from 0x1d7e57aa55817448
13import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
14// TokenList Imports
15import ViewResolvers from 0x15a918087ab12d86
16import FTViewUtils from 0x15a918087ab12d86
17
18/// Token List registry contract
19///
20access(all) contract TokenList {
21
22    /* --- Entitlement --- */
23
24    access(all) entitlement Maintainer
25    access(all) entitlement SuperAdmin
26
27    /* --- Events --- */
28
29    /// Event emitted when the contract is initialized
30    access(all) event ContractInitialized()
31
32    /// Event emitted when a new Fungible Token is registered
33    access(all) event FungibleTokenRegistered(
34        _ address: Address,
35        _ contractName: String,
36        _ type: Type,
37    )
38    /// Event emitted when a new Fungible Token is removed
39    access(all) event FungibleTokenRemoved(
40        _ address: Address,
41        _ contractName: String,
42        _ type: Type,
43    )
44    /// Event emitted when Reviewer Metadata is updated
45    access(all) event FungibleTokenReviewerMetadataUpdated(
46        _ reviewer: Address,
47        name: String?,
48        url: String?,
49    )
50    /// Event emitted when reviewer rank is updated
51    access(all) event FungibleTokenReviewerVerifiedUpdated(
52        _ reviewer: Address,
53        _ isVerified: Bool
54    )
55    /// Event emitted when reviewer rank is updated
56    access(all) event FungibleTokenReviewerRankUpdated(
57        _ reviewer: Address,
58        _ rank: UInt8
59    )
60    /// Event emitted when an Editable FTView is created
61    access(all) event FungibleTokenReviewerEditableFTViewCreated(
62        _ address: Address,
63        _ contractName: String,
64        id: UInt64,
65        reviewer: Address
66    )
67    /// Evenit emitted when an Editable FTDisplay is created
68    access(all) event FungibleTokenReviewerEditableFTDisplayCreated(
69        _ address: Address,
70        _ contractName: String,
71        reviewer: Address
72    )
73    /// Event emitted when a new Fungible Token is reviewed
74    access(all) event FungibleTokenViewResolverUpdated(
75        _ address: Address,
76        _ contractName: String,
77        _ newResolverIdentifier: String
78    )
79    /// Event emitted when a Fungible Token is evaluated
80    access(all) event FungibleTokenReviewEvaluated(
81        _ address: Address,
82        _ contractName: String,
83        _ rank: UInt8,
84        _ by: Address
85    )
86    /// Event emitted when a Fungible Token is commented
87    access(all) event FungibleTokenCommented(
88        _ address: Address,
89        _ contractName: String,
90        _ comment: String,
91        _ by: Address
92    )
93    /// Event emitted when a Fungible Token is tagged
94    access(all) event FungibleTokenTagsAdded(
95        _ address: Address,
96        _ contractName: String,
97        _ tags: [String],
98        _ by: Address
99    )
100
101    /* --- Variable, Enums and Structs --- */
102
103    access(all) let registryStoragePath: StoragePath
104    access(all) let registryPublicPath: PublicPath
105    access(all) let reviewerStoragePath: StoragePath
106    access(all) let reviewerPublicPath: PublicPath
107    access(all) let maintainerStoragePath: StoragePath
108
109    /* --- Interfaces & Resources --- */
110
111    /// Interface for the FT Entry
112    ///
113    access(all) resource interface FTEntryInterface {
114        /// Create an empty vault for the FT
115        access(all)
116        fun createEmptyVault(): @{FungibleToken.Vault}
117        // ----- View Methods -----
118        access(all)
119        view fun getIdentity(): FTViewUtils.FTIdentity
120        /// Get the FT Type
121        access(all)
122        view fun getTokenType(): Type
123        /// Check if the Fungible Token has a native view resolver
124        access(all)
125        view fun isNativeViewResolver(): Bool
126        /// Check if the Fungible Token is reviewed by some one
127        access(all)
128        view fun isReviewedBy(_ reviewer: Address): Bool
129        /// Get the Fungible Token Review
130        access(all)
131        view fun getFTReview(_ reviewer: Address): FTViewUtils.FTReview?
132        /// Get the display metadata of the FT, fallback to the highest rank reviewer
133        access(all)
134        fun getDisplay(_ reviewer: Address?): FTViewUtils.FTDisplayWithSource?
135        /// Get the vault info the FT, fallback to the highest rank reviewer
136        access(all)
137        fun getVaultData(_ reviewer: Address?): FTViewUtils.FTVaultDataWithSource?
138        // ----- Quick Access For FTViews -----
139        /// Get the evaluation rank of the Fungible Token, no fallback
140        access(all)
141        view fun getEvaluatedRank(_ reviewer: Address?): FTViewUtils.Evaluation? {
142            if let reviewerAddr = reviewer {
143                if let reviewRef = self.borrowReviewRef(reviewerAddr) {
144                    return reviewRef.getEvaluationRank()
145                }
146            }
147            return nil
148        }
149        /// Get the tags of the Fungiuble Token, fallback to the highest rank reviewer
150        access(all)
151        fun getTags(_ reviewer: Address?): [String] {
152            if let fallbackedReviewerAddr = self.tryGetReviewer(reviewer) {
153                if let reviewRef = self.borrowReviewRef(fallbackedReviewerAddr) {
154                    let returnTags = reviewRef.getTags()
155                    // Add extra tags based on the evaluation rank if the reviewer is the fallbackedReviewer
156                    if reviewer == fallbackedReviewerAddr {
157                        if reviewRef.evalRank.rawValue == FTViewUtils.Evaluation.BLOCKED.rawValue {
158                            returnTags.insert(at: 0, "Blocked")
159                        } else if reviewRef.evalRank.rawValue == FTViewUtils.Evaluation.FEATURED.rawValue {
160                            returnTags.insert(at: 0, "Featured")
161                            returnTags.insert(at: 0, "Verified")
162                        } else if reviewRef.evalRank.rawValue == FTViewUtils.Evaluation.VERIFIED.rawValue {
163                            returnTags.insert(at: 0, "Verified")
164                        } else if reviewRef.evalRank.rawValue == FTViewUtils.Evaluation.PENDING.rawValue {
165                            returnTags.insert(at: 0, "Pending")
166                        }
167                    }
168                    return returnTags
169                }
170            }
171            return []
172        }
173        // ----- Internal Methods: Used by Reviewer -----
174        /// Try to get a reviewer address
175        access(contract)
176        view fun tryGetReviewer(_ reviewer: Address?): Address? {
177            let tokenType = self.getTokenType()
178            var reviewerAddr = reviewer
179            if reviewerAddr == nil {
180                let registry = TokenList.borrowRegistry()
181                reviewerAddr = registry.getHighestRankCustomizedReviewer(tokenType)
182            }
183            return reviewerAddr
184        }
185        access(contract)
186        view fun borrowReviewRef(_ reviewer: Address): &FTViewUtils.FTReview?
187        /// Update the view resolver
188        access(contract)
189        fun updateViewResolver()
190        /// Add a new review to the FT
191        access(contract)
192        fun addReview(_ reviewer: Address, _ review: FTViewUtils.FTReview)
193    }
194
195    /// Resource for the Fungible Token Entry
196    ///
197    access(all) resource FungibleTokenEntry: FTEntryInterface {
198        access(all)
199        let identity: FTViewUtils.FTIdentity
200        // Reviewer => FTReview
201        access(self)
202        let reviewers: {Address: FTViewUtils.FTReview}
203        // view resolver
204        access(all)
205        var viewResolver: @{ViewResolver.Resolver}?
206
207        init(
208            _ ftAddress: Address,
209            _ ftContractName: String,
210        ) {
211            self.reviewers = {}
212            self.identity = FTViewUtils.FTIdentity(ftAddress, ftContractName)
213            // ensure the contract is a Fungible Token
214            let ftContractRef = self.identity.borrowFungibleTokenContract()
215            // If viewResolver is not provided, then create one using the ViewResolvers
216            if ViewResolvers.borrowContractViewResolver(ftAddress, ftContractName) != nil {
217                self.viewResolver <- ViewResolvers.createContractViewResolver(address: ftAddress, contractName: ftContractName)
218            } else {
219                let vaultType = FTViewUtils.buildFTVaultType(ftAddress, ftContractName) ?? panic("Could not build the FT Type")
220                let emptyVault <- ftContractRef.createEmptyVault(vaultType: vaultType)
221                if emptyVault.isInstance(Type<@{ViewResolver.Resolver}>()) {
222                    self.viewResolver <- emptyVault as @{ViewResolver.Resolver}
223                } else {
224                    self.viewResolver <- nil
225                    destroy emptyVault
226                }
227            }
228            // ensure ftView exists
229            self.getDisplay(nil)
230            self.getVaultData(nil)
231        }
232
233        // ----- Implementing the FTMetadataInterface -----
234
235
236        /// Create an empty vault for the FT
237        ///
238        access(all)
239        fun createEmptyVault(): @{FungibleToken.Vault} {
240            let ftContract = self.borrowFungibleTokenContract()
241            let ftType = self.identity.buildType()
242            return <- ftContract.createEmptyVault(vaultType: ftType)
243        }
244
245        /// Get the Fungible Token Identity
246        ///
247        access(all)
248        view fun getIdentity(): FTViewUtils.FTIdentity {
249            return self.identity
250        }
251
252        /// Check if the Fungible Token is reviewed by some one
253        ///
254        access(all)
255        view fun isReviewedBy(_ reviewer: Address): Bool {
256            return self.reviewers[reviewer] != nil
257        }
258
259        /// Get the Fungible Token Review
260        ///
261        access(all)
262        view fun getFTReview(_ reviewer: Address): FTViewUtils.FTReview? {
263            return self.reviewers[reviewer]
264        }
265
266        /// Get the display metadata of the FT
267        ///
268        access(all)
269        fun getDisplay(_ reviewer: Address?): FTViewUtils.FTDisplayWithSource? {
270            var source: Address? = nil
271            var retFTDisplay: FungibleTokenMetadataViews.FTDisplay? = nil
272            if let viewResolver = self.borrowViewResolver() {
273                retFTDisplay = FungibleTokenMetadataViews.getFTDisplay(viewResolver)
274            }
275            var reviewerAddr = self.tryGetReviewer(reviewer)
276            if let addr = reviewerAddr {
277                if let reviewerRef = TokenList.borrowReviewerPublic(addr) {
278                    if let ftDisplayRef = reviewerRef.borrowFTDisplayReader(self.getTokenType()) {
279                        let socials = retFTDisplay?.socials ?? {}
280                        let extraSocials = ftDisplayRef.getSocials()
281                        for key in extraSocials.keys {
282                            socials[key] = extraSocials[key]
283                        }
284                        source = addr
285                        retFTDisplay = FungibleTokenMetadataViews.FTDisplay(
286                            name: ftDisplayRef.getName() ?? retFTDisplay?.name ?? "Unkonwn",
287                            symbol: ftDisplayRef.getSymbol() ?? retFTDisplay?.symbol ?? "NONE",
288                            description: ftDisplayRef.getDescription() ?? retFTDisplay?.description ?? "No Description",
289                            externalURL: ftDisplayRef.getExternalURL() ?? retFTDisplay?.externalURL ?? MetadataViews.ExternalURL("https://fixes.world"),
290                            logos: ftDisplayRef.getLogos(),
291                            socials: socials
292                        )
293                    }
294                }
295            }
296            return retFTDisplay != nil
297                ? FTViewUtils.FTDisplayWithSource(source, retFTDisplay!)
298                : nil
299        }
300
301        /// Get the vault data of the FT
302        ///
303        access(all)
304        fun getVaultData(_ reviewer: Address?): FTViewUtils.FTVaultDataWithSource? {
305            var source: Address? = nil
306            var retVaultData: FungibleTokenMetadataViews.FTVaultData? = nil
307            // First try to get the data from the view resolver
308            if let viewResolver = self.borrowViewResolver() {
309                retVaultData = FungibleTokenMetadataViews.getFTVaultData(viewResolver)
310            }
311            if retVaultData != nil {
312                return FTViewUtils.FTVaultDataWithSource(nil, retVaultData!)
313            }
314            // If not found, then try to get the data from the reviewer
315            let tokenType = self.getTokenType()
316            var reviewerAddr = self.tryGetReviewer(reviewer)
317            if let addr = reviewerAddr {
318                if let reviewerRef = TokenList.borrowReviewerPublic(addr) {
319                    let ref = reviewerRef.borrowFTViewReader(tokenType)
320                    source = addr
321                    retVaultData = ref?.getFTVaultData()
322                }
323            }
324            return retVaultData != nil
325                ? FTViewUtils.FTVaultDataWithSource(source, retVaultData!)
326                : nil
327        }
328
329        /// Get the FT Type
330        access(all)
331        view fun getTokenType(): Type {
332            return self.identity.buildType()
333        }
334
335        /// Check if the Fungible Token has a native view resolver
336        access(all)
337        view fun isNativeViewResolver(): Bool {
338            return self.viewResolver != nil
339        }
340
341        // ----- Internal Methods -----
342
343        /// Update the view resolver
344        ///
345        access(contract)
346        fun updateViewResolver() {
347            if self.viewResolver != nil {
348                return // existing view resolver
349            }
350            if ViewResolvers.borrowContractViewResolver(
351                self.identity.address,
352                self.identity.contractName,
353            ) == nil {
354                return // no view resolver found
355            }
356            let newViewResolver <- ViewResolvers.createContractViewResolver(
357                address: self.identity.address,
358                contractName: self.identity.contractName
359            )
360            // ensure ftView exists
361            self.getDisplay(nil)
362            self.getVaultData(nil)
363
364            let old <- self.viewResolver <- newViewResolver
365            destroy old
366
367            // emit the event
368            emit FungibleTokenViewResolverUpdated(
369                self.identity.address,
370                self.identity.contractName,
371                self.viewResolver.getType().identifier
372            )
373        }
374
375        /// Add a new review to the FT
376        ///
377        access(contract)
378        fun addReview(_ reviewer: Address, _ review: FTViewUtils.FTReview) {
379            pre {
380                self.reviewers[reviewer] == nil:
381                    "Reviewer already exists"
382            }
383            self.reviewers[reviewer] = review
384        }
385
386        /// Borrow the review reference
387        ///
388        access(contract)
389        view fun borrowReviewRef(_ reviewer: Address): &FTViewUtils.FTReview? {
390            return &self.reviewers[reviewer]
391        }
392
393        /// Borrow the View Resolver
394        ///
395        access(contract)
396        view fun borrowViewResolver(): &{ViewResolver.Resolver}? {
397            return &self.viewResolver
398        }
399
400        /// Borrow the Fungible Token Contract
401        ///
402        access(contract)
403        fun borrowFungibleTokenContract(): &{FungibleToken} {
404            return self.identity.borrowFungibleTokenContract()
405        }
406    }
407
408    /// Interface for the Fungible Token Reviewer
409    ///
410    access(all) resource interface FungibleTokenReviewerInterface {
411        access(all)
412        view fun getAddress(): Address {
413            return self.owner?.address ?? panic("Owner not found")
414        }
415        access(all)
416        view fun getName(): String? {
417            let metadata = self.getMetadata()
418            return metadata["name"]
419        }
420        access(all)
421        view fun getUrl(): String? {
422            let metadata = self.getMetadata()
423            return metadata["url"]
424        }
425        access(all)
426        view fun getMetadata(): {String: String}
427        access(all)
428        view fun getManagedTokenAmount(): Int
429        access(all)
430        view fun getReviewedTokenAmount(): Int
431        access(all)
432        view fun getCustomizedTokenAmount(): Int
433        access(all)
434        view fun getManagedFTTypes(): [Type]
435        access(all)
436        view fun getReviewedFTTypes(): [Type]
437        access(all)
438        view fun getFeaturedFTTypes(): [Type]
439        access(all)
440        view fun getVerifiedFTTypes(): [Type]
441        access(all)
442        view fun getBlockedFTTypes(): [Type]
443        access(all)
444        view fun borrowFTViewReader(_ tokenType: Type): &FTViewUtils.EditableFTView?
445        access(all)
446        view fun borrowFTDisplayReader(_ tokenType: Type): &{FTViewUtils.FTViewDisplayEditor}?
447    }
448
449    /// Maintainer interface for the Fungible Token Reviewer
450    ///
451    access(all) resource interface FungibleTokenReviewMaintainer {
452        /// Update Reviewer Metadata
453        access(Maintainer)
454        fun updateMetadata(name: String?, url: String?)
455        /// Review the Fungible Token with Evaluation
456        access(Maintainer)
457        fun reviewFTEvalute(_ type: Type, rank: FTViewUtils.Evaluation)
458        /// Review the Fungible Token with Comment
459        access(Maintainer)
460        fun reviewFTComment(_ type: Type, comment: String)
461        /// Review the Fungible Token, add tags
462        access(Maintainer)
463        fun reviewFTAddTags(_ type: Type, tags: [String])
464        /// Register a new Fungible Token with the Editable FTView
465        access(Maintainer)
466        fun registerFungibleTokenWithEditableFTView(
467            _ ftAddress: Address,
468            _ ftContractName: String,
469            at: StoragePath
470        )
471        /// Register the Fungible Token Display Patch
472        access(Maintainer)
473        fun registerFungibleTokenDisplayPatch(
474            _ ftAddress: Address,
475            _ ftContractName: String
476        )
477        /// Borrow the FTView editor
478        access(Maintainer)
479        view fun borrowFTViewEditor(_ tokenType: Type): auth(FTViewUtils.Editable) &FTViewUtils.EditableFTView?
480        /// Borrow or create the FTDisplay editor
481        access(Maintainer)
482        view fun borrowFTDisplayEditor(_ tokenType: Type): auth(FTViewUtils.Editable) &{FTViewUtils.FTViewDisplayEditor}?
483    }
484
485    /// The resource for the FT Reviewer
486    ///
487    access(all) resource FungibleTokenReviewer: FungibleTokenReviewMaintainer, FungibleTokenReviewerInterface, ViewResolver.ResolverCollection {
488        access(self)
489        let metadata: {String: String}
490        access(self)
491        let storedIdMapping: {UInt64: Type}
492        access(self)
493        let storedDatas: @{Type: FTViewUtils.EditableFTView}
494        access(self)
495        let storedDisplayPatches: @{Type: FTViewUtils.EditableFTDisplay}
496        access(self)
497        let reviewed: {Type: FTViewUtils.Evaluation}
498
499        init() {
500            self.metadata = {}
501            self.storedIdMapping = {}
502            self.storedDatas <- {}
503            self.storedDisplayPatches <- {}
504            self.reviewed = {}
505        }
506
507        // --- Implement the FungibleTokenReviewMaintainer ---
508
509        /// Update Reviewer Metadata
510        ///
511        access(Maintainer)
512        fun updateMetadata(name: String?, url: String?) {
513            if name != nil {
514                self.metadata["name"] = name!
515            }
516
517            if url != nil {
518                self.metadata["url"] = url!
519            }
520
521            // emit the event
522            emit FungibleTokenReviewerMetadataUpdated(
523                self.getAddress(),
524                name: name,
525                url: url,
526            )
527        }
528
529        /// Review the Fungible Token with Evaluation
530        ///
531        access(Maintainer)
532        fun reviewFTEvalute(_ type: Type, rank: FTViewUtils.Evaluation) {
533            let registery = TokenList.borrowRegistry()
534            let entryRef = registery.borrowFungibleTokenEntry(type)
535                ?? panic("Failed to load the Fungible Token Entry")
536
537            let reviewerAddr = self.getAddress()
538            var isUpdated = false
539            if let reviewRef = entryRef.borrowReviewRef(reviewerAddr) {
540                if reviewRef.evalRank.rawValue != rank.rawValue {
541                    reviewRef.updateEvaluationRank(rank)
542                    isUpdated = true
543                }
544            } else {
545                entryRef.addReview(reviewerAddr, FTViewUtils.FTReview(rank))
546                isUpdated = true
547            }
548
549            // If not updated, then return
550            if !isUpdated {
551                return
552            }
553
554            // update reviewed status locally
555            self.reviewed[type] = rank
556
557            let identity = entryRef.getIdentity()
558
559            // emit the event
560            emit FungibleTokenReviewEvaluated(
561                identity.address,
562                identity.contractName,
563                rank.rawValue,
564                reviewerAddr
565            )
566        }
567
568        /// Review the Fungible Token with Comment
569        ///
570        access(Maintainer)
571        fun reviewFTComment(_ type: Type, comment: String) {
572            let registery = TokenList.borrowRegistry()
573            let entryRef = registery.borrowFungibleTokenEntry(type)
574                ?? panic("Failed to load the Fungible Token Entry")
575
576            let reviewerAddr = self.getAddress()
577            if entryRef.borrowReviewRef(reviewerAddr) == nil {
578                // add the review with UNVERIFIED evaluation
579                self.reviewFTEvalute(type, rank: FTViewUtils.Evaluation.UNVERIFIED)
580            }
581            let reviewRef = entryRef.borrowReviewRef(reviewerAddr)
582                ?? panic("Failed to load the Fungible Token Review")
583            reviewRef.addComment(comment, reviewerAddr)
584
585            let identity = entryRef.getIdentity()
586            // emit the event
587            emit FungibleTokenCommented(
588                identity.address,
589                identity.contractName,
590                comment,
591                reviewerAddr
592            )
593        }
594
595        /// Review the Fungible Token, add tags
596        ///
597        access(Maintainer)
598        fun reviewFTAddTags(_ type: Type, tags: [String]) {
599            pre {
600                tags.length > 0: "Tags should not be empty"
601            }
602
603            let registery = TokenList.borrowRegistry()
604            let entryRef = registery.borrowFungibleTokenEntry(type)
605                ?? panic("Failed to load the Fungible Token Entry")
606
607            let reviewerAddr = self.getAddress()
608            if entryRef.borrowReviewRef(reviewerAddr) == nil {
609                // add the review with UNVERIFIED evaluation
610                self.reviewFTEvalute(type, rank: FTViewUtils.Evaluation.UNVERIFIED)
611            }
612            let ref = entryRef.borrowReviewRef(reviewerAddr)
613                ?? panic("Failed to load the Fungible Token Review")
614            var isUpdated = false
615            for tag in tags {
616                // ingore the eval tags
617                if tag == "Verified" || tag == "Featured" || tag == "Pending" || tag == "Blocked" {
618                    continue
619                }
620                if ref.addTag(tag) {
621                    isUpdated = true
622                }
623            }
624
625            // If not updated, then return
626            if !isUpdated {
627                return
628            }
629
630            let identity = entryRef.getIdentity()
631            // emit the event
632            emit FungibleTokenTagsAdded(
633                identity.address,
634                identity.contractName,
635                tags,
636                reviewerAddr
637            )
638        }
639
640        /// Register a new Fungible Token with the Editable FTView
641        ///
642        access(Maintainer)
643        fun registerFungibleTokenWithEditableFTView(
644            _ ftAddress: Address,
645            _ ftContractName: String,
646            at: StoragePath
647        ) {
648            let registery = TokenList.borrowRegistry()
649            let tokenType = FTViewUtils.buildFTVaultType(ftAddress, ftContractName)
650                ?? panic("Could not build the FT Type")
651            // register the Fungible Token if not exists
652            if registery.borrowFungibleTokenEntry(tokenType) == nil {
653                registery.registerStandardFungibleToken(ftAddress, ftContractName)
654            }
655
656            assert(
657                self.storedDatas[tokenType] == nil,
658                message: "Editable FTView already exists"
659            )
660
661            // create a Editable FTView resource it the reviewer storage
662            let editableFTView <- FTViewUtils.createEditableFTView(ftAddress, ftContractName, at)
663            let ftViewId = editableFTView.uuid
664            // save in the resource
665            self.storedDatas[tokenType] <-! editableFTView
666            self.storedIdMapping[ftViewId] = tokenType
667
668            registery.onReviewerFTDataCustomized(self.getAddress(), tokenType)
669
670            // emit the event for editable FTView
671            emit FungibleTokenReviewerEditableFTViewCreated(
672                ftAddress,
673                ftContractName,
674                id: ftViewId,
675                reviewer: self.getAddress()
676            )
677        }
678
679        /// Borrow the FTView editor
680        ///
681        access(Maintainer)
682        view fun borrowFTViewEditor(_ tokenType: Type): auth(FTViewUtils.Editable) &FTViewUtils.EditableFTView? {
683            return self._borrowEditableFTView(tokenType)
684        }
685
686        /// Register the Fungible Token Display Patch
687        ///
688        access(Maintainer)
689        fun registerFungibleTokenDisplayPatch(
690            _ ftAddress: Address,
691            _ ftContractName: String
692        ) {
693            let registery = TokenList.borrowRegistry()
694
695            let tokenType = FTViewUtils.buildFTVaultType(ftAddress, ftContractName)
696                ?? panic("Could not build the FT Type")
697            assert(
698                registery.borrowFungibleTokenEntry(tokenType) != nil,
699                message: "Fungible Token not registered"
700            )
701            assert(
702                self.storedDisplayPatches[tokenType] == nil,
703                message: "Editable FTDisplay already exists"
704            )
705
706            // create a Editable FTDisplay resource it the reviewer storage
707            let editableFTDisplay <- FTViewUtils.createEditableFTDisplay(ftAddress, ftContractName)
708            let ftDisplayId = editableFTDisplay.uuid
709            // save in the resource
710            self.storedDisplayPatches[tokenType] <-! editableFTDisplay
711
712            registery.onReviewerFTDataCustomized(self.getAddress(), tokenType)
713
714            // emit the event for editable FTDisplay
715            emit FungibleTokenReviewerEditableFTDisplayCreated(
716                ftAddress,
717                ftContractName,
718                reviewer: self.getAddress()
719            )
720        }
721
722        /// Borrow or create the FTDisplay editor
723        ///
724        access(Maintainer)
725        view fun borrowFTDisplayEditor(_ tokenType: Type): auth(FTViewUtils.Editable) &{FTViewUtils.FTViewDisplayEditor}? {
726            let registery = TokenList.borrowRegistry()
727            if let ref = self._borrowEditableFTDisplay(tokenType) {
728                return ref
729            }
730            return self._borrowEditableFTView(tokenType)
731        }
732
733        // --- Implement the FungibleTokenReviewerInterface ---
734
735        access(all)
736        view fun getMetadata(): {String: String} {
737            return self.metadata
738        }
739
740        /// Return the amount of Fungible Token which managed by the reviewer
741        ///
742        access(all)
743        view fun getManagedTokenAmount(): Int {
744            return self.storedDatas.keys.length
745        }
746
747        /// Return the amount of Fungible Token which reviewed by the reviewer
748        ///
749        access(all)
750        view fun getReviewedTokenAmount(): Int {
751            return self.reviewed.keys.length
752        }
753
754        /// Return the amount of Fungible Token which display customized by the reviewer
755        ///
756        access(all)
757        view fun getCustomizedTokenAmount(): Int {
758            return self.storedDatas.keys.length + self.storedDisplayPatches.keys.length
759        }
760
761        /// Return all Fungible Token Types managed by the reviewer
762        ///
763        access(all)
764        view fun getManagedFTTypes(): [Type] {
765            return self.storedDatas.keys
766        }
767
768        /// Return all Fungible Token Types reviewed by the reviewer
769        ///
770        access(all)
771        view fun getReviewedFTTypes(): [Type] {
772            return self.reviewed.keys
773        }
774
775        /// Return all Fungible Token Types with FEATURED evaluation
776        ///
777        access(all)
778        view fun getFeaturedFTTypes(): [Type] {
779            let reviewedRef = &self.reviewed as &{Type: FTViewUtils.Evaluation}
780            return self.reviewed.keys.filter(view fun(type: Type): Bool {
781                return reviewedRef[type]?.rawValue == FTViewUtils.Evaluation.FEATURED.rawValue
782            })
783        }
784
785        /// Return all Fungible Token Types with VERIFIED or FEATURED evaluation
786        ///
787        access(all)
788        view fun getVerifiedFTTypes(): [Type] {
789            let reviewedRef = &self.reviewed as &{Type: FTViewUtils.Evaluation}
790            return self.reviewed.keys.filter(view fun(type: Type): Bool {
791                return reviewedRef[type]?.rawValue == FTViewUtils.Evaluation.VERIFIED.rawValue
792                    || reviewedRef[type]?.rawValue == FTViewUtils.Evaluation.FEATURED.rawValue
793            })
794        }
795
796        access(all)
797        view fun getBlockedFTTypes(): [Type] {
798            let reviewedRef = &self.reviewed as &{Type: FTViewUtils.Evaluation}
799            return self.reviewed.keys.filter(view fun(type: Type): Bool {
800                return reviewedRef[type]?.rawValue == FTViewUtils.Evaluation.BLOCKED.rawValue
801            })
802        }
803
804        /// Borrow the reference of Editable FT View
805        ///
806        access(all)
807        view fun borrowFTViewReader(_ tokenType: Type): &FTViewUtils.EditableFTView? {
808            return self._borrowEditableFTView(tokenType)
809        }
810
811        /// Borrow the FTDisplay editor
812        ///
813        access(all)
814        view fun borrowFTDisplayReader(_ tokenType: Type): &{FTViewUtils.FTViewDisplayEditor}? {
815            let ref = self._borrowEditableFTDisplay(tokenType)
816            if ref != nil {
817                return ref
818            }
819            return self._borrowEditableFTView(tokenType)
820        }
821
822        // --- Resource only methods ---
823
824        /// Create a customized view resolver for the Fungible Token
825        ///
826        access(all)
827        fun createCustomizedViewResolver(
828            _ ftAddress: Address,
829            _ ftContractName: String,
830        ): @{ViewResolver.Resolver} {
831            let tokenType = FTViewUtils.buildFTVaultType(ftAddress, ftContractName)
832                ?? panic("Could not build the FT Type")
833            let ref = self._borrowEditableFTView(tokenType)
834                ?? panic("Editable FTView not found")
835            return <- ViewResolvers.createCollectionViewResolver(
836                TokenList.getReviewerCapability(self.getAddress()),
837                id: ref.uuid
838            )
839        }
840
841        // --- Implement the ViewResolver.ResolverCollection ---
842
843        /// Get the IDs of the view resolvers
844        ///
845        access(all)
846        view fun getIDs(): [UInt64] {
847            return self.storedIdMapping.keys
848        }
849
850        /// Borrow the view resolver
851        ///
852        access(all)
853        view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver} {
854            var ret: &{ViewResolver.Resolver}? = nil
855            if let tokenType = self.storedIdMapping[id] {
856                ret = self._borrowEditableFTView(tokenType)
857            }
858            return ret ?? panic("Failed to borrow the view resolver")
859        }
860
861        // --- Internal Methods ---
862
863        /// Borrow the Editable FT View
864        ///
865        access(self)
866        view fun _borrowEditableFTView(_ tokenType: Type): auth(FTViewUtils.Editable) &FTViewUtils.EditableFTView? {
867            return &self.storedDatas[tokenType]
868        }
869
870        /// Borrow the Editable FT Display
871        ///
872        access(self)
873        view fun _borrowEditableFTDisplay(_ tokenType: Type): auth(FTViewUtils.Editable) &{FTViewUtils.FTViewDisplayEditor}? {
874            return &self.storedDisplayPatches[tokenType]
875        }
876    }
877
878    /// The Maintainer for the Fungible Token Reviewer
879    ///
880    access(all) resource ReviewMaintainer: FungibleTokenReviewMaintainer {
881        access(self)
882        let reviewerCap: Capability<auth(Maintainer) &FungibleTokenReviewer>
883
884        init(
885            _ cap: Capability<auth(Maintainer) &FungibleTokenReviewer>
886        ) {
887            pre {
888                cap.check(): "Invalid capability"
889            }
890            self.reviewerCap = cap
891        }
892
893        /// Get the reviewer address
894        ///
895        access(all)
896        view fun getReviewerAddress(): Address {
897            return self.reviewerCap.address
898        }
899
900        /// Update Reviewer Metadata
901        ///
902        access(Maintainer)
903        fun updateMetadata(name: String?, url: String?) {
904            self._borrowReviewer().updateMetadata(name: name, url: url)
905        }
906
907        /// Review the Fungible Token with Evaluation
908        ///
909        access(Maintainer)
910        fun reviewFTEvalute(_ type: Type, rank: FTViewUtils.Evaluation) {
911            self._borrowReviewer().reviewFTEvalute(type, rank: rank)
912        }
913
914        /// Review the Fungible Token with Comment
915        ///
916        access(Maintainer)
917        fun reviewFTComment(_ type: Type, comment: String) {
918            self._borrowReviewer().reviewFTComment(type, comment: comment)
919        }
920
921        /// Review the Fungible Token, add tags
922        ///
923        access(Maintainer)
924        fun reviewFTAddTags(_ type: Type, tags: [String]) {
925            self._borrowReviewer().reviewFTAddTags(type, tags: tags)
926        }
927
928        /// Register a new Fungible Token with the Editable FTView
929        ///
930        access(Maintainer)
931        fun registerFungibleTokenWithEditableFTView(
932            _ ftAddress: Address,
933            _ ftContractName: String,
934            at: StoragePath
935        ) {
936            self._borrowReviewer().registerFungibleTokenWithEditableFTView(ftAddress, ftContractName, at: at)
937        }
938
939        /// Register the Fungible Token Display Patch
940        ///
941        access(Maintainer)
942        fun registerFungibleTokenDisplayPatch(
943            _ ftAddress: Address,
944            _ ftContractName: String
945        ) {
946            self._borrowReviewer().registerFungibleTokenDisplayPatch(ftAddress, ftContractName)
947        }
948
949        /// Borrow the FTView editor
950        ///
951        access(Maintainer)
952        view fun borrowFTViewEditor(_ tokenType: Type): auth(FTViewUtils.Editable) &FTViewUtils.EditableFTView? {
953            return self._borrowReviewer().borrowFTViewEditor(tokenType)
954        }
955
956        /// Borrow the FTDisplay editor
957        ///
958        access(Maintainer)
959        view fun borrowFTDisplayEditor(_ tokenType: Type): auth(FTViewUtils.Editable) &{FTViewUtils.FTViewDisplayEditor}? {
960            return self._borrowReviewer().borrowFTDisplayEditor(tokenType)
961        }
962
963        /* ---- Internal Methods ---- */
964
965        access(self)
966        view fun _borrowReviewer(): auth(Maintainer) &FungibleTokenReviewer {
967            return self.reviewerCap.borrow() ?? panic("Failed to borrow the reviewer")
968        }
969    }
970
971    /// Interface for the Token List Viewer
972    ///
973    access(all) resource interface TokenListViewer {
974        // --- Read Methods ---
975        /// Return all available reviewers
976        access(all)
977        view fun getReviewers(): [Address]
978        /// Get the reviewer rank
979        access(all)
980        view fun getReviewerRank(_ reviewer: Address): ReviewerRank?
981        /// Return all verified reviewers
982        access(all)
983        view fun getVerifiedReviewers(): [Address]
984        /// Return if the reviewer is verified
985        access(all)
986        view fun isReviewerVerified(_ reviewer: Address): Bool
987        /// Get the amount of Fungible Token Entries
988        access(all)
989        view fun getFTEntriesAmount(): Int
990        /// Get all the Fungible Token Entries
991        access(all)
992        view fun getAllFTEntries(): [Type]
993        /// Get the Fungible Token Entry by the page and size
994        access(all)
995        view fun getFTEntries(_ page: Int, _ size: Int): [Type]
996        /// Get the Fungible Token Entry by the address
997        access(all)
998        view fun getFTEntriesByAddress(_ address: Address): [Type]
999        /// Get the customized reviewers
1000        access(all)
1001        view fun getHighestRankCustomizedReviewer(_ tokenType: Type): Address?
1002        /// Get the Fungible Token Entry by the type
1003        access(all)
1004        view fun borrowFungibleTokenEntry(_ tokenType: Type): &{FTEntryInterface}?
1005        // --- Write Methods ---
1006        /// Register a new standard Fungible Token Entry to the registry
1007        access(contract)
1008        fun registerStandardFungibleToken(_ ftAddress: Address, _ ftContractName: String)
1009        /// Invoked when a new Fungible Token is customized by the reviewer
1010        access(contract)
1011        fun onReviewerFTDataCustomized(_ reviewer: Address, _ tokenType: Type)
1012    }
1013
1014    access(all) enum ReviewerRank: UInt8 {
1015        access(all) case NORMAL
1016        access(all) case ADVANCED
1017        access(all) case EXPERT
1018    }
1019
1020    /// The Token List Registry
1021    ///
1022    access(all) resource Registry: TokenListViewer {
1023        // Address => isVerified
1024        access(self)
1025        let verifiedReviewers: {Address: Bool}
1026        access(self)
1027        let reviewerRanks: {Address: ReviewerRank}
1028        // FT Type => FT Entry
1029        access(self)
1030        let entries: @{Type: FungibleTokenEntry}
1031        // FT Entry ID => FT Type
1032        access(self)
1033        let entriesIdMapping: {UInt64: Type}
1034        // Address => [FTContractName]
1035        access(self)
1036        let addressMapping: {Address: [String]}
1037        // Customized FT Views
1038        access(self)
1039        let customizedFTViews: {Type: [Address]}
1040
1041        init() {
1042            self.verifiedReviewers = {}
1043            self.reviewerRanks = {}
1044            self.entriesIdMapping = {}
1045            self.entries <- {}
1046            self.addressMapping = {}
1047            self.customizedFTViews = {}
1048        }
1049
1050        // ----- Read Methods -----
1051
1052        /// Return all available reviewers
1053        ///
1054        access(all)
1055        view fun getReviewers(): [Address] {
1056            return self.reviewerRanks.keys
1057        }
1058
1059        /// Get the reviewer rank
1060        ///
1061        access(all)
1062        view fun getReviewerRank(_ reviewer: Address): ReviewerRank? {
1063            return self.reviewerRanks[reviewer]
1064        }
1065
1066        /// Return all verified reviewers
1067        ///
1068        access(all)
1069        view fun getVerifiedReviewers(): [Address] {
1070            return self.verifiedReviewers.keys
1071        }
1072
1073        /// Return if the reviewer is verified
1074        ///
1075        access(all)
1076        view fun isReviewerVerified(_ reviewer: Address): Bool {
1077            return self.verifiedReviewers[reviewer] ?? false
1078        }
1079
1080        /// Get the amount of Fungible Token Entries
1081        access(all)
1082        view fun getFTEntriesAmount(): Int {
1083            return self.entries.keys.length
1084        }
1085
1086        /// Get all the Fungible Token Entries
1087        ///
1088        access(all)
1089        view fun getAllFTEntries(): [Type] {
1090            return self.entries.keys
1091        }
1092
1093        /// Get the Fungible Token Entry by the page and size
1094        ///
1095        access(all)
1096        view fun getFTEntries(_ page: Int, _ size: Int): [Type] {
1097            pre {
1098                page >= 0: "Invalid page"
1099                size > 0: "Invalid size"
1100            }
1101            let max = self.getFTEntriesAmount()
1102            let start = page * size
1103            if start > max {
1104                return []
1105            }
1106            var end = start + size
1107            if end > max {
1108                end = max
1109            }
1110            return self.entries.keys.slice(from: start, upTo: end)
1111        }
1112
1113        /// Get the Fungible Token Entry by the address
1114        ///
1115        access(all)
1116        view fun getFTEntriesByAddress(_ address: Address): [Type] {
1117            if let contracts = self.borrowAddressContractsRef(address) {
1118                var types: [Type] = []
1119                for contractName in contracts {
1120                    if let type = FTViewUtils.buildFTVaultType(address, contractName) {
1121                        types = types.concat([type])
1122                    }
1123                }
1124                return types
1125            }
1126            return []
1127        }
1128
1129        /// Get the highest rank customized reviewers
1130        ///
1131        access(all)
1132        view fun getHighestRankCustomizedReviewer(_ tokenType: Type): Address? {
1133            if let reviewers = self.customizedFTViews[tokenType] {
1134                var highestRank: ReviewerRank? = nil
1135                var highestReviewer: Address? = nil
1136                for reviewer in reviewers {
1137                    if let rank = self.reviewerRanks[reviewer] {
1138                        if highestRank == nil || rank.rawValue > highestRank!.rawValue || self.verifiedReviewers[reviewer] == true {
1139                            highestRank = rank
1140                            highestReviewer = reviewer
1141                            // break if the reviewer is verified
1142                            if self.verifiedReviewers[reviewer] == true {
1143                                break
1144                            }
1145                        }
1146                    }
1147                }
1148                return highestReviewer
1149            }
1150            return nil
1151        }
1152
1153        /// Get the Fungible Token Entry by the type
1154        access(all)
1155        view fun borrowFungibleTokenEntry(_ tokenType: Type): &{FTEntryInterface}? {
1156            return self.borrowFungibleTokenEntryWritableRef(tokenType)
1157        }
1158
1159        // ----- Write Methods -----
1160
1161        /// Update the reviewer verified status
1162        ///
1163        access(SuperAdmin)
1164        fun updateReviewerVerified(_ reviewer: Address, _ verified: Bool) {
1165            pre {
1166                TokenList.borrowReviewerPublic(reviewer) != nil: "FT Reviewer not found"
1167            }
1168            self.verifiedReviewers[reviewer] = verified
1169
1170            // emit the event
1171            emit FungibleTokenReviewerVerifiedUpdated(
1172                reviewer,
1173                verified
1174            )
1175        }
1176
1177        /// Remove a Fungible Token Entry from the registry
1178        ///
1179        access(SuperAdmin)
1180        fun removeFungibleToken(_ type: Type): Bool {
1181            return self._removeFungibleToken(type)
1182        }
1183
1184        /// Register a new standard Fungible Token Entry to the registry
1185        ///
1186        access(contract)
1187        fun registerStandardFungibleToken(_ ftAddress: Address, _ ftContractName: String) {
1188            pre {
1189                TokenList.isFungibleTokenRegistered(ftAddress, ftContractName) == false: "Fungible Token already registered"
1190            }
1191            self._registerFungibleToken(<- create FungibleTokenEntry(ftAddress, ftContractName))
1192        }
1193
1194        /// Invoked when a new Fungible Token is customized by the reviewer
1195        ///
1196        access(contract)
1197        fun onReviewerFTDataCustomized(_ reviewer: Address, _ tokenType: Type) {
1198            pre {
1199                TokenList.borrowReviewerPublic(reviewer) != nil: "FT Reviewer not found"
1200            }
1201            // ensure the tokenType exists
1202            if self.customizedFTViews[tokenType] == nil {
1203                self.customizedFTViews[tokenType] = []
1204            }
1205            // add the reviewer to the list
1206            if let arrRef = &self.customizedFTViews[tokenType] as &[Address]? {
1207                if arrRef.firstIndex(of: reviewer) == nil {
1208                    self.customizedFTViews[tokenType]?.append(reviewer)
1209                }
1210            }
1211            // update the rank
1212            self._updateReviewerRank(reviewer)
1213        }
1214
1215        // ----- Internal Methods -----
1216
1217        access(self)
1218        fun _updateReviewerRank(_ reviewer: Address) {
1219            let reviewerRef = TokenList.borrowReviewerPublic(reviewer)
1220                ?? panic("Could not find the FT Reviewer")
1221            let oldRank = self.reviewerRanks[reviewer]
1222            var newRank: TokenList.ReviewerRank? = nil
1223            // ensure the reviewer rank exists
1224            if self.reviewerRanks[reviewer] == nil {
1225                newRank = ReviewerRank.NORMAL
1226            } else {
1227                // update the rank by the amount of customized FT
1228                let managedAmt = reviewerRef.getManagedTokenAmount()
1229                let reviewedAmt = reviewerRef.getReviewedTokenAmount()
1230                let customizedAmt = reviewerRef.getCustomizedTokenAmount()
1231                let weight = managedAmt * 10 + customizedAmt * 5 + reviewedAmt
1232                if weight >= 1000 {
1233                    newRank = ReviewerRank.EXPERT
1234                } else if weight >= 200 {
1235                    newRank = ReviewerRank.ADVANCED
1236                }
1237            }
1238
1239            if newRank != nil && oldRank != newRank {
1240                self.reviewerRanks[reviewer] = newRank!
1241                // emit the event
1242                emit FungibleTokenReviewerRankUpdated(
1243                    reviewer,
1244                    newRank!.rawValue
1245                )
1246            }
1247        }
1248
1249        /// Remove a Fungible Token Entry from the registry
1250        ///
1251        access(self)
1252        fun _removeFungibleToken(_ type: Type): Bool {
1253            if self.entries[type] == nil {
1254                return false
1255            }
1256            let entry <- self.entries.remove(key: type)
1257                ?? panic("Could not remove the Fungible Token Entry")
1258            self.entriesIdMapping.remove(key: entry.uuid)
1259
1260            let identity = entry.getIdentity()
1261            if let addrRef = self.borrowAddressContractsRef(identity.address) {
1262                if let idx = addrRef.firstIndex(of: identity.contractName) {
1263                    addrRef.remove(at: idx)
1264                }
1265            }
1266            destroy entry
1267
1268            // emit the event
1269            emit FungibleTokenRemoved(
1270                identity.address,
1271                identity.contractName,
1272                type
1273            )
1274            return true
1275        }
1276
1277        /// Add a new Fungible Token Entry to the registry
1278        ///
1279        access(self)
1280        fun _registerFungibleToken(_ ftEntry: @FungibleTokenEntry) {
1281            pre {
1282                self.entries[ftEntry.getTokenType()] == nil:
1283                    "FungibleToken Entry already exists in the registry"
1284                self.entriesIdMapping[ftEntry.uuid] == nil:
1285                    "FungibleToken Entry ID already exists in the registry"
1286            }
1287            let tokenType = ftEntry.getTokenType()
1288            self.entries[tokenType] <-! ftEntry
1289
1290            let ref = self.borrowFungibleTokenEntryWritableRef(tokenType)
1291                ?? panic("Could not borrow the FT Entry")
1292            // Add the ID mapping
1293            self.entriesIdMapping[ref.uuid] = tokenType
1294            // Add the address mapping
1295            if let addrRef = self.borrowAddressContractsRef(ref.identity.address) {
1296                addrRef.append(ref.identity.contractName)
1297            } else {
1298                self.addressMapping[ref.identity.address] = [ref.identity.contractName]
1299            }
1300
1301            // emit the event
1302            emit FungibleTokenRegistered(
1303                ref.identity.address,
1304                ref.identity.contractName,
1305                tokenType
1306            )
1307        }
1308
1309        access(self)
1310        view fun borrowFungibleTokenEntryWritableRef(_ tokenType: Type): &FungibleTokenEntry? {
1311            return &self.entries[tokenType]
1312        }
1313
1314        access(self)
1315        view fun borrowAddressContractsRef(_ addr: Address): auth(Mutate) &[String]? {
1316            return &self.addressMapping[addr]
1317        }
1318    }
1319
1320    /* --- Public Methods --- */
1321
1322    /// Create a new Fungible Token Reviewer
1323    ///
1324    access(all)
1325    fun createFungibleTokenReviewer(): @FungibleTokenReviewer {
1326        return <- create FungibleTokenReviewer()
1327    }
1328
1329    /// Create a new Fungible Token Review Maintainer
1330    ///
1331    access(all)
1332    fun createFungibleTokenReviewMaintainer(
1333        _ cap: Capability<auth(Maintainer) &FungibleTokenReviewer>
1334    ): @ReviewMaintainer {
1335        return <- create ReviewMaintainer(cap)
1336    }
1337
1338    /// Get the Fungible Token Reviewer capability
1339    ///
1340    access(all)
1341    view fun getReviewerCapability(_ addr: Address): Capability<&FungibleTokenReviewer> {
1342        return getAccount(addr)
1343            .capabilities.get<&FungibleTokenReviewer>(
1344                self.reviewerPublicPath
1345            )
1346    }
1347
1348    /// Borrow the public capability of the Fungible Token Reviewer
1349    ///
1350    access(all)
1351    view fun borrowReviewerPublic(_ addr: Address): &FungibleTokenReviewer? {
1352        return self.getReviewerCapability(addr).borrow()
1353    }
1354
1355    /// Borrow the public capability of  Token List Registry
1356    ///
1357    access(all)
1358    view fun borrowRegistry(): &{TokenListViewer} {
1359        return getAccount(self.account.address)
1360            .capabilities.get<&{TokenListViewer}>(self.registryPublicPath)
1361            .borrow()
1362            ?? panic("Could not borrow the Registry reference")
1363    }
1364
1365    /// Check if the Fungible Token is registered
1366    ///
1367    access(all)
1368    view fun isFungibleTokenRegistered(_ address: Address, _ contractName: String): Bool {
1369        let registry: &{TokenList.TokenListViewer} = self.borrowRegistry()
1370        if let ftType = FTViewUtils.buildFTVaultType(address, contractName) {
1371            return registry.borrowFungibleTokenEntry(ftType) != nil
1372        }
1373        return false
1374    }
1375
1376    /// Check if the Fungible Token is registered with the native view resolver
1377    ///
1378    access(all)
1379    view fun isFungibleTokenRegisteredWithNativeViewResolver(_ address: Address, _ contractName: String): Bool {
1380        let registry: &{TokenList.TokenListViewer} = self.borrowRegistry()
1381        if let ftType = FTViewUtils.buildFTVaultType(address, contractName) {
1382            if let entryRef = registry.borrowFungibleTokenEntry(ftType) {
1383                return entryRef.isNativeViewResolver()
1384            }
1385        }
1386        return false
1387    }
1388
1389    /// Check if the NFT is valid to register
1390    access(all)
1391    fun isValidToRegister(_ address: Address, _ contractName: String): Bool {
1392        if let contractRef = ViewResolvers.borrowContractViewResolver(address, contractName) {
1393            let displayType = Type<FungibleTokenMetadataViews.FTDisplay>()
1394            let dataType = Type<FungibleTokenMetadataViews.FTVaultData>()
1395            let contractViews = contractRef.getContractViews(resourceType: nil)
1396            if contractViews.contains(dataType) == false || contractViews.contains(displayType) == false {
1397                return false
1398            }
1399            var convertedDisplayView: FungibleTokenMetadataViews.FTDisplay? = nil
1400            var convertedDataView: FungibleTokenMetadataViews.FTVaultData? = nil
1401            if let displayView = contractRef.resolveContractView(resourceType: nil, viewType: displayType) {
1402                convertedDisplayView = displayView as? FungibleTokenMetadataViews.FTDisplay
1403            } else {
1404                return false
1405            }
1406            if let dataView = contractRef.resolveContractView(resourceType: nil, viewType: dataType) {
1407                convertedDataView = dataView as? FungibleTokenMetadataViews.FTVaultData
1408            } else {
1409                return false
1410            }
1411            if convertedDisplayView != nil && convertedDataView != nil {
1412                return true
1413            }
1414        }
1415        return false
1416    }
1417
1418    /// Try to register a new Fungible Token, if already registered, then do nothing
1419    ///
1420    access(all)
1421    fun ensureFungibleTokenRegistered(_ ftAddress: Address, _ ftContractName: String) {
1422        if !self.isFungibleTokenRegistered(ftAddress, ftContractName) && self.isValidToRegister(ftAddress, ftContractName) {
1423            let registry = self.borrowRegistry()
1424            registry.registerStandardFungibleToken(ftAddress, ftContractName)
1425        }
1426    }
1427
1428    /// Update View Resolver for the Fungible Token
1429    ///
1430    access(all)
1431    fun updateFungibleTokenViewResolver(
1432        _ ftAddress: Address,
1433        _ ftContractName: String,
1434    ) {
1435        self.ensureFungibleTokenRegistered(ftAddress, ftContractName)
1436        let registry = self.borrowRegistry()
1437        if let ftType = FTViewUtils.buildFTVaultType(ftAddress, ftContractName) {
1438            if let entryRef = registry.borrowFungibleTokenEntry(ftType) {
1439                entryRef.updateViewResolver()
1440            }
1441        }
1442    }
1443
1444    /// The prefix for the paths
1445    ///
1446    access(all)
1447    view fun getPathPrefix(): String {
1448        return "TokenList_".concat(self.account.address.toString()).concat("_")
1449    }
1450
1451    /// Generate the review maintainer capability ID
1452    ///
1453    access(all)
1454    view fun generateReviewMaintainerCapabilityId(_ addr: Address): String {
1455        return TokenList.getPathPrefix().concat("PrivateIdentity_ReviewMaintainer_".concat(addr.toString()))
1456    }
1457
1458    init() {
1459        // Identifiers
1460        let identifier = TokenList.getPathPrefix()
1461        self.registryStoragePath = StoragePath(identifier: identifier.concat("_Registry"))!
1462        self.registryPublicPath = PublicPath(identifier: identifier.concat("_Registry"))!
1463
1464        self.reviewerStoragePath = StoragePath(identifier: identifier.concat("_Reviewer"))!
1465        self.reviewerPublicPath = PublicPath(identifier: identifier.concat("_Reviewer"))!
1466
1467        self.maintainerStoragePath = StoragePath(identifier: identifier.concat("_Maintainer"))!
1468
1469        // Create the Token List Registry
1470        let registry <- create Registry()
1471        self.account.storage.save(<- registry, to: self.registryStoragePath)
1472        // link the public capability
1473        let cap = self.account.capabilities
1474            .storage.issue<&{TokenListViewer}>(self.registryStoragePath)
1475        self.account.capabilities.publish(cap, at: self.registryPublicPath)
1476
1477        // Emit the initialized event
1478        emit ContractInitialized()
1479    }
1480}
1481