Smart Contract
TokenList
A.15a918087ab12d86.TokenList
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