Smart Contract

FRC20NFTWrapper

A.d2abb5dbf5e08666.FRC20NFTWrapper

Valid From

86,128,672

Deployed

3d ago
Feb 24, 2026, 11:54:28 PM UTC

Dependents

3 imports
1/**
2> Author: Fixes Lab <https://github.com/fixes-world/>
3
4# FRC20NFTWrapper
5
6The FRC20NFTWrapper contract is a contract that allows users to wrap their NFTs with FRC20 tokens.
7The contract is designed to be used with the Fixes ecosystem, and it allows users to wrap their NFTs with FRC20 tokens
8that are minted by the Fixes FRC20 contract.
9
10*/
11import MetadataViews from 0x1d7e57aa55817448
12import ViewResolver from 0x1d7e57aa55817448
13import NonFungibleToken from 0x1d7e57aa55817448
14import FlowToken from 0x1654653399040a61
15import StringUtils from 0xa340dc0a4ec828ab
16import NFTCatalog from 0x49a7cda3a1eecc29
17// Fixes Imports
18import Fixes from 0xd2abb5dbf5e08666
19import FixesInscriptionFactory from 0xd2abb5dbf5e08666
20import FixesWrappedNFT from 0xd2abb5dbf5e08666
21import FRC20Indexer from 0xd2abb5dbf5e08666
22
23access(all) contract FRC20NFTWrapper {
24
25    access(all) entitlement Manage
26    access(all) entitlement Admin
27
28    /// The event that is emitted when the contract is created
29    access(all) event ContractInitialized()
30
31    /// The event that is emitted when the internal flow vault is donated to
32    access(all) event InternalFlowVaultDonated(amount: UFix64)
33
34    /// The event that is emitted when a new Wrapper is created
35    access(all) event WrapperCreated()
36    /// The event that is emitted when the wrapper options is updated
37    access(all) event WrapperOptionsUpdated(wrapper: Address, key: String)
38
39    /// The event that is emitted when the whitelist is updated
40    access(all) event AuthorizedWhitelistUpdated(
41        addr: Address,
42        isAuthorized: Bool,
43    )
44
45    /// The event that is emitted when an NFT is unwrapped
46    access(all) event FRC20StrategyRegistered(
47        wrapper: Address,
48        deployer: Address,
49        nftType: Type,
50        tick: String,
51        alloc: UFix64,
52        copies: UInt64,
53        cond: String?
54    )
55    /// The event that is emitted when an NFT is wrapped
56    access(all) event NFTWrappedWithFRC20Allocated(
57        wrapper: Address,
58        nftType: Type,
59        srcNftId: UInt64,
60        wrappedNftId: UInt64,
61        tick: String,
62        alloc: UFix64,
63        address: Address,
64    )
65
66    // Indexer
67    /// The event that is emitted when a new wrapper is added to the indexer
68    access(all) event WrapperAddedToIndexer(wrapper: Address)
69    /// The event that is emitted when the extra NFT collection display is updated
70    access(all) event WrapperIndexerUpdatedNFTCollectionDisplay(nftType: Type, name: String, description: String)
71
72    /* --- Variable, Enums and Structs --- */
73    access(all)
74    let FRC20NFTWrapperStoragePath: StoragePath
75    access(all)
76    let FRC20NFTWrapperPublicPath: PublicPath
77    access(all)
78    let FRC20NFTWrapperIndexerStoragePath: StoragePath
79    access(all)
80    let FRC20NFTWrapperIndexerPublicPath: PublicPath
81
82
83    /* --- Interfaces & Resources --- */
84
85    access(all) struct FRC20Strategy {
86        access(all) let tick: String
87        access(all) let nftType: Type
88        access(all) let alloc: UFix64
89        access(all) let copies: UInt64
90        access(all) let cond: String?
91        access(all) let createdAt: UFix64
92        access(all) var usedAmt: UInt64
93
94        view init(
95            tick: String,
96            nftType: Type,
97            alloc: UFix64,
98            copies: UInt64,
99            cond: String?
100        ) {
101            self.tick = tick
102            self.nftType = nftType
103            self.alloc = alloc
104            self.copies = copies
105            self.cond = cond
106            self.usedAmt = 0
107            self.createdAt = getCurrentBlock().timestamp
108        }
109
110        access(all)
111        view fun isUsedUp(): Bool {
112            return self.usedAmt >= self.copies
113        }
114
115        access(contract)
116        fun use() {
117            pre {
118                self.usedAmt < self.copies: "The strategy is used up"
119            }
120            self.usedAmt = self.usedAmt + 1
121        }
122    }
123
124
125    access(all) resource interface WrapperPublic {
126        // public methods ----
127
128        /// Get the internal flow vault balance
129        access(all)
130        view fun getInternalFlowBalance(): UFix64
131
132        access(all)
133        view fun hasFRC20Strategy(_ collectionType: Type): Bool
134
135        access(all)
136        view fun getStrategiesAmount(all: Bool): UInt64
137
138        access(all)
139        view fun getStrategies(all: Bool): [FRC20Strategy]
140
141        access(all)
142        view fun isAuthorizedToRegister(addr: Address): Bool
143
144        access(all)
145        view fun getWhitelistedAddresses(): [Address]
146
147        access(all)
148        view fun getOption(key: String): AnyStruct?
149
150        access(all)
151        view fun getOptions(): {String: AnyStruct}
152
153        access(all)
154        fun isFRC20NFTWrappered(nft: &{NonFungibleToken.NFT}): Bool
155
156        // write methods ----
157
158        /// Donate to the internal flow vault
159        access(all)
160        fun donate(value: @FlowToken.Vault): Void
161
162        /// Register a new FRC20 strategy
163        access(all)
164        fun registerFRC20Strategy(
165            type: Type,
166            alloc: UFix64,
167            copies: UInt64,
168            cond: String?,
169            ins: auth(Fixes.Extractable) &Fixes.Inscription,
170        )
171
172        /// Xerox an NFT and wrap it to the FixesWrappedNFT collection
173        ///
174        access(all)
175        fun wrap(
176            recipient: &FixesWrappedNFT.Collection,
177            nftToWrap: @{NonFungibleToken.NFT}
178        ): UInt64
179    }
180
181    /// The resource for the Wrapper contract
182    ///
183    access(all) resource Wrapper: WrapperPublic {
184        access(self)
185        let strategies: {Type: FRC20Strategy}
186        access(self)
187        let histories: {Type: {UInt64: Bool}}
188        access(self)
189        let internalFlowVault: @FlowToken.Vault
190        access(self)
191        let whitelist: {Address: Bool}
192        access(self)
193        let options: {String: AnyStruct}
194
195        init() {
196            self.histories = {}
197            self.strategies = {}
198            self.whitelist = {}
199            self.options = {}
200            self.internalFlowVault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>())
201
202            emit WrapperCreated()
203        }
204
205        // public methods
206
207        access(all)
208        view fun getInternalFlowBalance(): UFix64 {
209            return self.internalFlowVault.balance
210        }
211
212        access(all)
213        fun isFRC20NFTWrappered(nft: &{NonFungibleToken.NFT}): Bool {
214            let collectionType = FRC20NFTWrapper.asCollectionType(nft.getType().identifier)
215            if let nftHistories = &self.histories[collectionType] as &{UInt64: Bool}? {
216                return nftHistories[nft.id] ?? false
217            }
218            return false
219        }
220
221        access(all)
222        view fun hasFRC20Strategy(_ collectionType: Type): Bool {
223            return self.strategies[collectionType] != nil
224        }
225
226        access(all)
227        view fun getStrategiesAmount(all: Bool): UInt64 {
228            if all {
229                return UInt64(self.strategies.length)
230            }
231            return UInt64(self.strategies.values.filter(view fun (s: FRC20Strategy): Bool {
232                return s.isUsedUp() == false
233            }).length)
234        }
235
236        access(all)
237        view fun getStrategies(all: Bool): [FRC20Strategy] {
238            if all {
239                return self.strategies.values
240            }
241            return self.strategies.values.filter(view fun (s: FRC20Strategy): Bool {
242                return s.isUsedUp() == false
243            })
244        }
245
246        access(all)
247        view fun isAuthorizedToRegister(addr: Address): Bool {
248            return addr == self.owner?.address || (self.whitelist[addr] ?? false)
249        }
250
251        access(all)
252        view fun getWhitelistedAddresses(): [Address] {
253            let refDict = &self.whitelist as &{Address: Bool}
254            return self.whitelist.keys.filter(view fun (addr: Address): Bool {
255                return refDict[addr]! == true
256            })
257        }
258
259        access(all)
260        view fun getOption(key: String): AnyStruct? {
261            return self.options[key]
262        }
263
264        access(all)
265        view fun getOptions(): {String: AnyStruct} {
266            return self.options
267        }
268
269        // write methods
270
271        access(all)
272        fun donate(value: @FlowToken.Vault): Void {
273            pre {
274                value.balance > 0.0: "Donation must be greater than 0"
275            }
276            let amt = value.balance
277            self.internalFlowVault.deposit(from: <- value)
278            emit InternalFlowVaultDonated(amount: amt)
279        }
280
281        /// Register a new FRC20 strategy
282        access(all)
283        fun registerFRC20Strategy(
284            type: Type,
285            alloc: UFix64,
286            copies: UInt64,
287            cond: String?,
288            ins: auth(Fixes.Extractable) &Fixes.Inscription,
289        ) {
290            pre {
291                ins.isExtractable(): "The inscription is not extractable"
292            }
293            let indexer = FRC20Indexer.getIndexer()
294            assert(
295                indexer.isValidFRC20Inscription(ins: ins),
296                message: "The inscription is not a valid FRC20 inscription"
297            )
298            let fromAddr = ins.owner?.address ?? panic("Inscription owner is nil")
299            let data = FixesInscriptionFactory.parseMetadata(ins.borrowData())
300            assert(
301                data["op"] == "transfer" && data["tick"] != nil && data["amt"] != nil && data["to"] != nil,
302                message: "The inscription is not a valid FRC20 inscription for transfer"
303            )
304            let tick: String = data["tick"]!.toLower()
305            let meta = indexer.getTokenMeta(tick: tick)
306                ?? panic("Could not get token meta for ".concat(tick))
307
308            /// check if the deployer is the owner of the inscription
309            assert(
310                meta.deployer == fromAddr,
311                message: "The frc20 deployer is not the owner of the inscription"
312            )
313
314            // check if the deployer is authorized to register a new strategy
315            assert(
316                self.isAuthorizedToRegister(addr: fromAddr),
317                message: "The deployer is not authorized to register a new strategy"
318            )
319
320            // ensure store as collection type
321            let collectionType = FRC20NFTWrapper.asCollectionType(type.identifier)
322            assert(
323                collectionType.identifier != Type<@FixesWrappedNFT.Collection>().identifier,
324                message: "You cannot wrap a FixesWrappedNFT"
325            )
326
327            // check if the strategy already exists
328            assert(
329                self.strategies[collectionType] == nil,
330                message: "The strategy already exists"
331            )
332
333            // ensure condition is valid
334            if let condStr = cond {
335                let conds = StringUtils.split(condStr, ",")
336                for one in conds {
337                    let subConds = StringUtils.split(one, "~")
338                    if subConds.length == 1 && UInt64.fromString(subConds[0]) != nil {
339                        continue
340                    } else if subConds.length == 2 && UInt64.fromString(subConds[0]) != nil && UInt64.fromString(subConds[1]) != nil {
341                        continue
342                    } else {
343                        panic("Invalid condition")
344                    }
345                }
346            }
347
348            // indexer address
349            let indexerAddr = FRC20Indexer.getAddress()
350
351            // check if the allocation is enough
352            let amt = UFix64.fromString(data["amt"]!) ?? panic("The amount is not a valid UFix64")
353            let to = Address.fromString(data["to"]!) ?? panic("The receiver is not a valid address")
354            let toAllocateAmt = alloc * UFix64(copies)
355            assert(
356                amt >= toAllocateAmt,
357                message: "The amount is not enough to allocate"
358            )
359            assert(
360                to == indexerAddr,
361                message: "The receiver is not the indexer"
362            )
363
364            // apply inscription for transfer
365            indexer.transfer(ins: ins)
366
367            // ensure frc20 is enough
368            let frc20BalanceForContract = indexer.getBalance(tick: tick, addr: indexerAddr)
369            assert(
370                frc20BalanceForContract >= toAllocateAmt,
371                message: "The FRC20 balance for the contract is not enough"
372            )
373
374            // setup strategy
375            self.strategies[collectionType] = FRC20Strategy(
376                tick: tick,
377                nftType: collectionType,
378                alloc: alloc,
379                copies: copies,
380                cond: cond,
381            )
382            // setup history
383            self.histories[collectionType] = {}
384
385            // emit event
386            emit FRC20StrategyRegistered(
387                wrapper: self.owner?.address ?? panic("Wrapper owner is nil"),
388                deployer: fromAddr,
389                nftType: collectionType,
390                tick: tick,
391                alloc: alloc,
392                copies: copies,
393                cond: cond,
394            )
395        }
396
397        /// Wrap an NFT and wrap it to the FixesWrappedNFT collection
398        ///
399        access(all)
400        fun wrap(
401            recipient: &FixesWrappedNFT.Collection,
402            nftToWrap: @{NonFungibleToken.NFT}
403        ): UInt64 {
404            // check if the NFT is owned by the signer
405            let recipientAddr = recipient.owner?.address ?? panic("Recipient owner is nil")
406            // get the NFT type
407            let nftTypeIdentifier = nftToWrap.getType().identifier
408            // generate the collection type
409            let nftType = FRC20NFTWrapper.asCollectionType(nftTypeIdentifier)
410            // get the NFT id
411            let srcNftId = nftToWrap.id
412            // check if the strategy exists, and borrow it
413            let strategy = self.borrowStrategy(nftType: nftType)
414            // check if the strategy is used up
415            assert(
416                strategy.usedAmt < strategy.copies,
417                message: "The strategy is used up"
418            )
419
420            // check strategy condition
421            if let condStr = strategy.cond {
422                var valid: Bool = false
423                let conds = StringUtils.split(condStr, ",")
424                for one in conds {
425                    let subConds = StringUtils.split(one, "~")
426                    if subConds.length == 1 {
427                        // check if the NFT id is the same
428                        valid = valid || UInt64.fromString(subConds[0]) == srcNftId
429                    } else if subConds.length == 2 {
430                        // check if the NFT id is in the range
431                        let start = UInt64.fromString(subConds[0]) ?? panic("Invalid condition")
432                        let end = UInt64.fromString(subConds[1]) ?? panic("Invalid condition")
433                        // check if the range is valid, and the NFT id is in the range
434                        // NOTE: the range is [start, end)
435                        valid = valid || (start <= srcNftId && srcNftId < end)
436                    } else {
437                      panic("Invalid condition")
438                    }
439                    // break if valid
440                    if valid {
441                        break
442                    }
443                }
444
445                assert(
446                    valid,
447                    message: "The NFT ID does not meet the condition:".concat(condStr)
448                )
449            }
450
451            // borrow the history
452            let history = self.borrowHistory(nftType: nftType)
453            // check if the NFT is already wrapped
454            assert(
455                history[nftToWrap.id] == nil,
456                message: "The NFT is already wrapped"
457            )
458
459            // basic attributes
460            let mimeType = "text/plain"
461            let metaProtocol = "frc20"
462            let dataStr = "op=alloc,tick=".concat(strategy.tick)
463                .concat(",amt=").concat(strategy.alloc.toString())
464                .concat(",to=").concat(recipientAddr.toString())
465            let metadata = dataStr.utf8
466
467            // estimate the required storage
468            let estimatedReqValue = Fixes.estimateValue(
469                index: Fixes.totalInscriptions,
470                mimeType: mimeType,
471                data: metadata,
472                protocol: metaProtocol,
473                encoding: nil
474            )
475
476            // Get a reference to the signer's stored vault
477            let flowToReserve <- self.internalFlowVault.withdraw(amount: estimatedReqValue)
478
479            // Create the Inscription first
480            let newIns <- Fixes.createInscription(
481                // Withdraw tokens from the signer's stored vault
482                value: <- (flowToReserve as! @FlowToken.Vault),
483                mimeType: mimeType,
484                metadata: metadata,
485                metaProtocol: metaProtocol,
486                encoding: nil,
487                parentId: nil
488            )
489            // mint the wrapped NFT
490            let newId = FixesWrappedNFT.wrap(recipient: recipient, nftToWrap: <- nftToWrap, inscription: <- newIns)
491
492            // borrow the inscription
493            let nft = recipient.borrowFixesWrappedNFT(newId) ?? panic("Could not borrow FixesWrappedNFT")
494            let insRef = nft.borrowInscription() ?? panic("Could not borrow inscription")
495
496            // get FRC20 indexer
497            let indexer = FRC20Indexer.getIndexer()
498            let used <- indexer.allocate(ins: insRef)
499
500            // deposit the unused flow back to the internal flow vault
501            self.internalFlowVault.deposit(from: <- used)
502
503            // update strategy used one time
504            strategy.use()
505
506            // update histories
507            history[srcNftId] = true
508
509            // emit event
510            emit NFTWrappedWithFRC20Allocated(
511                wrapper: self.owner?.address ?? panic("Wrapper owner is nil"),
512                nftType: nftType,
513                srcNftId: srcNftId,
514                wrappedNftId: newId,
515                tick: strategy.tick,
516                alloc: strategy.alloc,
517                address: recipientAddr,
518            )
519
520            return newId
521        }
522
523        // private methods
524
525        /// Update the wrapper options
526        access(Manage)
527        fun updateOptions(key: String, value: AnyStruct) {
528            self.options[key] = value
529
530            emit WrapperOptionsUpdated(
531                wrapper: self.owner?.address ?? panic("Wrapper owner is nil"),
532                key: key
533            )
534        }
535
536        /// Update the whitelist
537        ///
538        access(Manage)
539        fun updateWhitelist(addr: Address, isAuthorized: Bool): Void {
540            self.whitelist[addr] = isAuthorized
541
542            emit AuthorizedWhitelistUpdated(
543                addr: addr,
544                isAuthorized: isAuthorized,
545            )
546        }
547
548        // internal methods
549
550        /// Borrow the strategy for an NFT type
551        ///
552        access(self)
553        view fun borrowStrategy(nftType: Type): &FRC20Strategy {
554            return &self.strategies[nftType] as &FRC20Strategy?
555                ?? panic("Could not borrow strategy")
556        }
557
558        /// Borrow the history for an NFT type
559        ///
560        access(self)
561        view fun borrowHistory(nftType: Type): auth(Mutate) &{UInt64: Bool} {
562            return &self.histories[nftType] as auth(Mutate) &{UInt64: Bool}?
563                ?? panic("Could not borrow history")
564        }
565    }
566
567    /// The public resource interface for the Wrapper Indexer
568    ///
569    access(all) resource interface WrapperIndexerPublic {
570        // public methods ----
571
572        /// Check if the wrapper is registered
573        ///
574        access(all)
575        view fun hasRegisteredWrapper(addr: Address): Bool
576
577        /// Get all the wrappers
578        access(all)
579        view fun getAllWrappers(_ includeNoStrategy: Bool, _ includeFinished: Bool): [Address]
580
581        /// Get the public reference to the Wrapper resource
582        ///
583        access(all)
584        view fun borrowWrapperPublic(addr: Address): &{WrapperPublic}? {
585            return FRC20NFTWrapper.borrowWrapperPublic(addr: addr)
586        }
587
588        /// Get the NFT collection display
589        ///
590        access(all)
591        fun getNFTCollectionDisplay(
592            nftType: Type,
593        ): MetadataViews.NFTCollectionDisplay
594
595        // ---- write methods ----
596
597        /// Register a new Wrapper
598        access(all)
599        fun registerWrapper(wrapper: auth(Manage) &Wrapper)
600    }
601
602    /// The resource for the Wrapper indexer contract
603    ///
604    access(all) resource WrapperIndexer: WrapperIndexerPublic {
605        /// The event that is emitted when the contract is created
606        access(self)
607        let wrappers: {Address: Bool}
608        access(self)
609        let displayHelper: {Type: MetadataViews.NFTCollectionDisplay}
610
611        init() {
612            self.wrappers = {}
613            self.displayHelper = {}
614        }
615
616        // public methods ----
617
618        /// Check if the wrapper is registered
619        ///
620        access(all)
621        view fun hasRegisteredWrapper(addr: Address): Bool {
622            return self.wrappers[addr] != nil
623        }
624
625        /// Get all the wrappers
626        ///
627        access(all)
628        view fun getAllWrappers(
629            _ includeNoStrategy: Bool,
630            _ includeFinished: Bool,
631        ): [Address] {
632            return self.wrappers.keys.filter(view fun (addr: Address): Bool {
633                if let wrapper = FRC20NFTWrapper.borrowWrapperPublic(addr: addr) {
634                    return includeNoStrategy ? true : wrapper.getStrategiesAmount(all: includeFinished) > 0
635                } else {
636                    return false
637                }
638            })
639        }
640
641        /// Get the extra NFT collection display
642        ///
643        access(all)
644        fun getNFTCollectionDisplay(
645            nftType: Type,
646        ): MetadataViews.NFTCollectionDisplay {
647            let collectionType = FRC20NFTWrapper.asCollectionType(nftType.identifier)
648            let nftType = FRC20NFTWrapper.asNFTType(nftType.identifier)
649            // get from NFTCatalog first
650            if let entries: {String: Bool} = NFTCatalog.getCollectionsForType(nftTypeIdentifier: nftType.identifier) {
651                for colId in entries.keys {
652                    if let catalogEntry = NFTCatalog.getCatalogEntry(collectionIdentifier: colId) {
653                        return catalogEntry.collectionDisplay
654                    }
655                }
656            }
657            // if no exists, then get from display helper
658            if let display = self.displayHelper[collectionType] {
659                return display
660            }
661            let defaultDisplay = FixesWrappedNFT.resolveContractView(resourceType: Type<@FixesWrappedNFT.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>()) as! MetadataViews.NFTCollectionDisplay?
662                ?? panic("Could not resolve default display")
663            let ids = StringUtils.split(nftType.identifier, ".")
664            return MetadataViews.NFTCollectionDisplay(
665                name: ids[2],
666                description: "NFT Collection built by address ".concat(ids[1]),
667                externalURL: defaultDisplay.externalURL,
668                squareImage: defaultDisplay.squareImage,
669                bannerImage: defaultDisplay.bannerImage,
670                socials: defaultDisplay.socials
671            )
672        }
673
674        // ---- write methods ----
675
676        /// Register a new Wrapper
677        access(all)
678        fun registerWrapper(wrapper: auth(Manage) &Wrapper) {
679            pre {
680                wrapper.owner != nil: "Wrapper owner is nil"
681            }
682            let ownerAddr = wrapper.owner!.address
683            self.wrappers[ownerAddr] = true
684
685            emit WrapperAddedToIndexer(wrapper: ownerAddr)
686        }
687
688        // ---- private write methods ----
689
690        access(Admin)
691        fun updateExtraNFTCollectionDisplay(
692            nftType: Type,
693            display: MetadataViews.NFTCollectionDisplay,
694        ): Void {
695            let collectionType = FRC20NFTWrapper.asCollectionType(nftType.identifier)
696            self.displayHelper[collectionType] = display
697
698            emit WrapperIndexerUpdatedNFTCollectionDisplay(
699                nftType: collectionType,
700                name: display.name,
701                description: display.description,
702            )
703        }
704    }
705
706    /// Donate to the internal flow vault
707    ///
708    access(all)
709    fun donate(
710        addr: Address,
711        _ value: @FlowToken.Vault
712    ): Void {
713        let ref = self.borrowWrapperPublic(addr: addr)
714             ?? panic("Could not borrow Xerox public reference")
715        ref.donate(value: <- value)
716    }
717
718    /// Create a new Wrapper resourceTON
719    ///
720    access(all)
721    fun createNewWrapper(): @Wrapper {
722        return <- create Wrapper()
723    }
724
725    /// Make a new NFT type
726    ///
727    access(all)
728    fun asNFTType(_ identifier: String): Type {
729        let ids = StringUtils.split(identifier, ".")
730        assert(ids.length == 4, message: "Invalid NFT type identifier!")
731        ids[3] = "NFT" // replace the last part with NFT
732        return CompositeType(StringUtils.join(ids, "."))!
733    }
734
735    /// Make a new NFT Collection type
736    ///
737    access(all)
738    fun asCollectionType(_ identifier: String): Type {
739        let ids = StringUtils.split(identifier, ".")
740        assert(ids.length == 4, message: "Invalid NFT Collection type identifier!")
741        ids[3] = "Collection" // replace the last part with Collection
742        return CompositeType(StringUtils.join(ids, "."))!
743    }
744
745    /// Borrow the public reference to the Wrapper resource
746    ///
747    access(all)
748    view fun borrowWrapperPublic(
749        addr: Address,
750    ): &{WrapperPublic}? {
751        return getAccount(addr)
752            .capabilities.get<&{WrapperPublic}>(self.FRC20NFTWrapperPublicPath)
753            .borrow()
754    }
755
756    /// Borrow the public interface to the Wrapper Indexer resource
757    ///
758    access(all)
759    view fun borrowWrapperIndexerPublic(): &{WrapperIndexerPublic} {
760        return getAccount(self.account.address)
761            .capabilities.get<&{WrapperIndexerPublic}>(self.FRC20NFTWrapperIndexerPublicPath)
762            .borrow()
763            ?? panic("Could not borrow WrapperIndexer public reference")
764    }
765
766    /// Get the NFT collection display
767    ///
768    access(all)
769    fun getNFTCollectionDisplay(
770        nftType: Type,
771    ): MetadataViews.NFTCollectionDisplay {
772        return self.borrowWrapperIndexerPublic().getNFTCollectionDisplay(nftType: nftType)
773    }
774
775    /// init
776    init() {
777        let identifier = "FixesFRC20NFTWrapper_".concat(self.account.address.toString())
778        self.FRC20NFTWrapperStoragePath = StoragePath(identifier: identifier)!
779        self.FRC20NFTWrapperPublicPath = PublicPath(identifier: identifier)!
780
781        self.FRC20NFTWrapperIndexerStoragePath = StoragePath(identifier: identifier.concat("_indexer"))!
782        self.FRC20NFTWrapperIndexerPublicPath = PublicPath(identifier: identifier.concat("_indexer"))!
783
784        // default wrapper
785        self.account.storage.save(<- self.createNewWrapper(), to: FRC20NFTWrapper.FRC20NFTWrapperStoragePath)
786        self.account.capabilities.publish(
787            self.account.capabilities.storage.issue<&FRC20NFTWrapper.Wrapper>(FRC20NFTWrapper.FRC20NFTWrapperStoragePath),
788            at: FRC20NFTWrapper.FRC20NFTWrapperPublicPath
789        )
790
791        // create indexer
792        let indexer <- create WrapperIndexer()
793        // save the indexer
794        self.account.storage.save(<- indexer, to: self.FRC20NFTWrapperIndexerStoragePath)
795        self.account.capabilities.publish(
796            self.account.capabilities.storage.issue<&FRC20NFTWrapper.WrapperIndexer>(self.FRC20NFTWrapperIndexerStoragePath),
797            at: self.FRC20NFTWrapperIndexerPublicPath
798        )
799
800        let indexerRef = self.account.storage.borrow<&FRC20NFTWrapper.WrapperIndexer>(from: self.FRC20NFTWrapperIndexerStoragePath)
801            ?? panic("Could not borrow indexer public reference")
802
803        // register the wrapper to the indexer
804        let wrapper = self.account.storage
805            .borrow<auth(Manage) &Wrapper>(from: FRC20NFTWrapper.FRC20NFTWrapperStoragePath)
806            ?? panic("Could not borrow wrapper public reference")
807        indexerRef.registerWrapper(wrapper: wrapper)
808
809        emit ContractInitialized()
810    }
811}
812