Smart Contract

Bag

A.c649103ebbc38926.Bag

Valid From

128,977,341

Deployed

1w ago
Feb 20, 2026, 09:25:16 AM UTC

Dependents

1 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import FungibleToken from 0xf233dcee88fe0abe
3import FlowToken from 0x1654653399040a61
4import MetadataViews from 0x1d7e57aa55817448
5import ViewResolver from 0x1d7e57aa55817448
6
7import Base64Util from 0xa45ead1cf1ca9eda
8import BagRegistry from 0xc649103ebbc38926
9
10import Background from 0xc649103ebbc38926
11import Body from 0xc649103ebbc38926
12import Cloth from 0xc649103ebbc38926
13import Weapon from 0xc649103ebbc38926
14import Glove from 0xc649103ebbc38926
15import Ring from 0xc649103ebbc38926
16import Helmet from 0xc649103ebbc38926
17import Rarity from 0xc649103ebbc38926
18
19access(all) contract Bag: NonFungibleToken, ViewResolver {
20
21    /* --- Events --- */
22    access(all) event ContractInitialized()
23    access(all) event NFTWithdrawn(id: UInt64, from: Address?)
24    access(all) event NFTDeposited(id: UInt64, to: Address?)
25    access(all) event NFTMinted(id: UInt64, svg: String, mintedFor: Address)
26    access(all) event PriceUpdated(oldPrice: UFix64, newPrice: UFix64) 
27
28    /* --- Storage Paths --- */
29    access(all) let CollectionStoragePath: StoragePath
30    access(all) let CollectionPublicPath: PublicPath
31    access(all) let CollectionPrivatePath: PrivatePath
32    access(all) let AdminStoragePath: StoragePath
33
34    /* --- Contract State --- */
35    access(all) var totalSupply: UInt64
36    access(all) let maxSupply: UInt64
37    access(all) var mintPrice: UFix64
38    access(all) let reservedSupply: UInt64
39    access(all) var reservedMinted: UInt64
40    access(all) var traitsDetails: {UInt64: Bag.TraitsDetails}
41    access(all) var bagRarityScores: {UInt64: UInt64} 
42    access(all) var uniqueTraitsCombinations: [String]
43    access(all) let registryAddress: Address
44    access(self) let owner: Address
45
46    // Price update configuration
47    access(all) var priceUpdateInterval: UInt64
48    access(all) let priceIncreaseAmount: UFix64
49    
50    access(all) struct TraitsDetails {
51        access(all) var background: String
52        access(all) var body: String
53        access(all) var cloth: String
54        access(all) var glove: String
55        access(all) var helmet: String
56        access(all) var ring: String
57        access(all) var weapon: String
58
59        init(background: String, body: String, cloth: String, glove: String, helmet: String, ring: String, weapon: String) {
60            self.background = background
61            self.body = body
62            self.cloth = cloth
63            self.glove = glove
64            self.helmet = helmet
65            self.ring = ring
66            self.weapon = weapon
67        }
68    }
69
70    access(all) resource NFT: NonFungibleToken.NFT {
71        access(all) let id: UInt64
72        access(all) let svg: String
73        access(all) var winCount: UInt64
74        access(all) var rarityScore: UInt64
75
76        init(id: UInt64, svg: String, rarityScore: UInt64) {
77            self.id = id
78            self.svg = svg
79            self.rarityScore = rarityScore
80            self.winCount = 0
81        }
82
83        access(all) view fun getSVG(): String {
84            return self.svg
85        }
86
87        access(all) view fun getWinCount(): UInt64 {
88            return self.winCount
89        }
90
91        access(all) view fun getRarityScore(): UInt64 {
92            return self.rarityScore
93        }
94
95        access(all) fun updateRarityScore(newScore: UInt64) {
96            self.rarityScore = newScore
97        }
98
99        access(all) fun incrementWinCount() {
100            self.winCount = self.winCount + 1
101        }
102
103        access(all) view fun getViews(): [Type] {
104            return [
105                Type<MetadataViews.Display>(),
106                Type<MetadataViews.ExternalURL>(),
107                Type<MetadataViews.Traits>(),
108                Type<MetadataViews.Royalties>(),
109                Type<MetadataViews.NFTView>(),
110                Type<MetadataViews.NFTCollectionData>(),
111                Type<MetadataViews.NFTCollectionDisplay>()
112            ]
113        }
114
115        access(all) fun resolveView(_ view: Type): AnyStruct? {
116            switch view {
117                case Type<MetadataViews.Display>():
118                    return MetadataViews.Display(
119                        name: "Bag # ".concat(self.id.toString()),
120                        description: "Bag (OCB) is a fully on-chain GameFi asset built on the Flow blockchain, powered by Flow VRF randomness to ensure complete fairness. Each Bag contains 7 unique traits, forming a one-of-a-kind warrior identity. Bag is not just about art — every mint amount is staked into Flow nodes, generating real yield that flows back to holders. It's identity, utility, and rewards, all packed into a single Bag.",
121                        thumbnail: MetadataViews.HTTPFile(url: self.getSVG())
122                    ) 
123                case Type<MetadataViews.ExternalURL>():
124                    return MetadataViews.ExternalURL("https://onchainbag.xyz")
125                case Type<MetadataViews.Traits>():
126                    return Bag.resolveNFTTraits(nftId: self.id)
127                case Type<MetadataViews.Royalties>():
128                    let owner = getAccount(Bag.owner)
129                    let cut = MetadataViews.Royalty(
130                        receiver: owner.capabilities.get<&{FungibleToken.Receiver}>(/public/dapperUtilityCoinReceiver),
131                        cut: 0.05, // 5% royalty
132                        description: "Creator Royalty"
133                    )
134                    var royalties: [MetadataViews.Royalty] = [cut]
135                    return MetadataViews.Royalties(royalties)
136                case Type<MetadataViews.NFTView>():
137                    let display = self.resolveView(Type<MetadataViews.Display>())! as! MetadataViews.Display
138                    let externalURL = self.resolveView(Type<MetadataViews.ExternalURL>())! as! MetadataViews.ExternalURL
139                    let collectionData = Bag.resolveContractView(
140                            resourceType: self.getType(),
141                            viewType: Type<MetadataViews.NFTCollectionData>()
142                        )! as! MetadataViews.NFTCollectionData
143                    let collectionDisplay = Bag.resolveContractView(
144                            resourceType: self.getType(),
145                            viewType: Type<MetadataViews.NFTCollectionDisplay>()
146                        )! as! MetadataViews.NFTCollectionDisplay
147                    let royalties = self.resolveView(Type<MetadataViews.Royalties>())! as! MetadataViews.Royalties
148                    let traits = self.resolveView(Type<MetadataViews.Traits>())! as! MetadataViews.Traits
149                    return MetadataViews.NFTView(
150                        id: self.id,
151                        uuid: self.uuid,
152                        display: display,
153                        externalURL: externalURL,
154                        collectionData: collectionData,
155                        collectionDisplay: collectionDisplay,
156                        royalties: royalties,
157                        traits: traits
158                    )
159                case Type<MetadataViews.NFTCollectionData>():
160                    return Bag.resolveContractView(resourceType: Type<@NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
161                case Type<MetadataViews.NFTCollectionDisplay>():
162                    return Bag.resolveContractView(resourceType: Type<@NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
163                default:
164                    return nil
165            } 
166        }
167
168        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
169            return <- Bag.createEmptyCollection(nftType: Type<@Bag.NFT>())
170        }  
171    }
172
173    access(all) resource interface CollectionPublic {
174        access(all) view fun getLength(): Int
175        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
176        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?
177        access(all) fun forEachID(_ f: fun (UInt64): Bool): Void
178        access(all) fun deposit(token: @{NonFungibleToken.NFT})
179        access(all) fun borrowBagNFT(id: UInt64): &Bag.NFT? {
180            post {
181                (result == nil) || (result?.id == id):
182                    "Cannot borrow Bag reference: The ID of the returned reference is incorrect"
183            }
184        }
185    }
186    access(all) resource Collection: CollectionPublic, NonFungibleToken.Collection {
187        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
188
189        init() {
190            self.ownedNFTs <- {}
191        }
192
193        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
194            pre {
195                self.ownedNFTs.containsKey(withdrawID): 
196                    "NFT with ID ".concat(withdrawID.toString()).concat(" not found in collection")
197            }
198
199            let owner = self.owner?.address!
200            let token <- self.ownedNFTs.remove(key: withdrawID)!
201            let id = token.id
202
203            // Call BagRegistry to unregister holder
204            if let ownerAddress = self.owner?.address {
205                let registryRef = getAccount(Bag.registryAddress).contracts.borrow<&BagRegistry>(name: "BagRegistry")
206                let _ = registryRef?.onNFTWithdrawn(bagId: id, from: owner)!
207            }
208            emit NFTWithdrawn(id: id, from: owner)
209            return <- token
210        }
211
212        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
213            return {Type<@Bag.NFT>(): true}
214        }
215
216        access(all) view fun isSupportedNFTType(type: Type): Bool {
217            return self.getSupportedNFTTypes()[type] ?? false
218        }
219
220        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
221            pre {
222                self.isSupportedNFTType(type: token.getType()):
223                    "Unsupported NFT type: ".concat(token.getType().identifier)
224                self.ownedNFTs[token.id] == nil: 
225                    "NFT with ID ".concat(token.id.toString()).concat(" already exists in collection")
226            }
227            
228            let token <- token as! @Bag.NFT
229            let id = token.id
230            let existingToken <- self.ownedNFTs[id] <- token
231            emit NFTDeposited(id: id, to: self.owner?.address)
232            destroy existingToken
233            // Call BagRegistry to register holder
234            if let ownerAddress = self.owner?.address {
235                let registryRef = getAccount(Bag.registryAddress).contracts.borrow<&BagRegistry>(name: "BagRegistry")
236                let _ = registryRef?.onNFTDeposited(bagId: id, to: ownerAddress)!
237            }
238        }
239
240        access(all) view fun getCollectionSize(): Int {
241            return self.ownedNFTs.length
242        }
243
244        access(all) fun forEachNFTId(_ callback: fun (UInt64): Bool) {
245            self.ownedNFTs.forEachKey(callback)
246        }
247
248        access(all) view fun getIDs(): [UInt64] {
249            return self.ownedNFTs.keys
250        }
251
252        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
253            return &self.ownedNFTs[id]
254        }
255
256        access(all) fun borrowBagNFT(id: UInt64): &Bag.NFT? {
257            if self.ownedNFTs[id] != nil {
258                let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
259                let bagNFT = ref as! &Bag.NFT
260                return bagNFT
261            }else{
262                return nil
263            }
264        }
265
266        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection}{ 
267			return <- Bag.createEmptyCollection(nftType: Type<@Bag.NFT>())
268		}
269    }
270
271    access(all) resource Admin {
272        access(all) fun mintReservedNFT(recipient: Address): @Bag.NFT {
273            pre {
274                Bag.reservedMinted < Bag.reservedSupply : 
275                    "No reserved NFTs available. Reserved supply: ".concat(Bag.reservedSupply.toString())
276                recipient == Bag.owner: "Only contract owner can mint reserved NFTs"
277            }
278            
279            Bag.totalSupply = Bag.totalSupply + 1
280            Bag.reservedMinted = Bag.reservedMinted + 1
281            
282            let id = Bag.totalSupply
283            let svg = Bag.createSVG()
284            let rarityScore = Bag.bagRarityScores[id] 
285                ?? panic("Rarity score not found for NFT ID: ".concat(id.toString()))
286            
287            var newNFT <- create NFT(id: id, svg: svg, rarityScore: rarityScore)
288            emit NFTMinted(id: newNFT.id, svg: newNFT.svg, mintedFor: recipient)
289            
290            return <- newNFT
291        }
292    }
293
294    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
295        return <- create Collection()
296    }
297
298    access(all) fun borrowNFT(ownerAddress: Address, nftId: UInt64): &Bag.NFT {
299        let collectionRef = getAccount(ownerAddress)
300            .capabilities
301            .borrow<&Bag.Collection>(Bag.CollectionPublicPath)
302            ?? panic("Cannot borrow collection reference from address: ".concat(ownerAddress.toString()))
303        
304        let nftRef = collectionRef.borrowBagNFT(id: nftId)
305            ?? panic("NFT not found with ID: ".concat(nftId.toString()))
306        
307        return nftRef
308    }
309
310    access(self) fun getRandomBackground(): String {
311        let index = Bag.generateRandomIndex(upperBound: Background.backgrounds.length)
312        return Background.backgrounds[index]
313    }
314
315    access(self) fun getRandomBody(): String {
316        let index = Bag.generateRandomIndex(upperBound: Body.body.length)
317        return Body.body[index]
318    }
319
320    access(self) fun getRandomCloth(): String {
321        let index = Bag.generateRandomIndex(upperBound: Cloth.cloths.length)
322        return Cloth.cloths[index]
323    }
324
325    access(self) fun getRandomWeapon(): String {
326        let index = Bag.generateRandomIndex(upperBound: Weapon.weapons.length)
327        return Weapon.weapons[index]
328    }
329
330    access(self) fun getRandomGlove(): String {
331        let index = Bag.generateRandomIndex(upperBound: Glove.gloves.length)
332        return Glove.gloves[index]
333    }
334
335    access(self) fun getRandomRing(): String {
336        let index = Bag.generateRandomIndex(upperBound: Ring.rings.length)
337        return Ring.rings[index]
338    }
339
340    access(self) fun getRandomHelmet(): String {
341        let index = Bag.generateRandomIndex(upperBound: Helmet.helmets.length)
342        return Helmet.helmets[index]
343    }
344
345    access(self) fun generateRandomIndex(upperBound: Int): Int {
346        assert(upperBound > 0, message: "Upper bound must be greater than 0")
347        let randomValue: UInt64 = revertibleRandom<UInt64>()
348        return Int(randomValue % UInt64(upperBound))
349    }
350
351    access(self) fun getRarityScoreValue(rarity: String): UInt64 {
352        switch rarity {
353            case "Common": return 1
354            case "Rare": return 2
355            case "Epic": return 3
356            case "Legendary": return 5
357            default: 
358                panic("Unknown rarity type: ".concat(rarity))
359        }
360    }
361
362    access(self) fun calculateRarityScore(itemName: String): UInt64 {
363        let rarity = Rarity.rarity[itemName] ?? "Common"
364        return Bag.getRarityScoreValue(rarity: rarity)
365    }
366
367    access(self) fun generateSVG(): String {
368        var totalRarityScore: UInt64 = 0
369        let bagId = Bag.totalSupply
370        
371        var svgContent = "<svg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='xMinYMin meet' viewBox='0 0 350 350'>"
372        svgContent = svgContent.concat("<style>.bag { fill: white; font-family: serif; font-size: 14px; font-weight: bold} .base { fill: white; font-family: serif; font-size: 14px; } .title { fill: #ddd; font-family: Bookman; font-size: 10px; text-anchor: middle; }</style>")
373        svgContent = svgContent.concat("<rect width='100%' height='100%' fill='black' />")
374        svgContent = svgContent.concat("<text x='175' y='340' class='title'>build-on-flow</text>")
375        svgContent = svgContent.concat("<text x='10' y='20' class='bag'>bag #".concat(bagId.toString()).concat("</text>"))
376        
377        var background: String = ""
378        var body: String = ""
379        var cloth: String = ""
380        var weapon: String = ""
381        var glove: String = ""
382        var ring: String = ""
383        var helmet: String = ""
384        var traitsKey: String = ""
385        
386        var attempts = 0
387        let maxAttempts = 100
388
389        while attempts < maxAttempts {
390            attempts = attempts + 1
391            
392            background = Bag.getRandomBackground()
393            body = Bag.getRandomBody()
394            cloth = Bag.getRandomCloth()
395            weapon = Bag.getRandomWeapon()
396            glove = Bag.getRandomGlove()
397            ring = Bag.getRandomRing()
398            helmet = Bag.getRandomHelmet()
399            
400            traitsKey = Bag.generateTraitsKey(
401                background: background,
402                body: body,
403                cloth: cloth,
404                weapon: weapon,
405                glove: glove,
406                ring: ring,
407                helmet: helmet
408            )
409            
410            if !self.uniqueTraitsCombinations.contains(traitsKey) {
411                break
412            }
413
414            if attempts == maxAttempts {
415                panic("Failed to generate unique traits combination after ".concat(maxAttempts.toString()).concat(" attempts"))
416            }
417        }
418        
419        self.uniqueTraitsCombinations.append(traitsKey)
420        
421        // Add traits to SVG
422        let traits = [background, body, cloth, weapon, glove, ring, helmet]
423        var yPosition = 60
424        
425        for trait in traits {
426            svgContent = svgContent.concat("<text x='10' y='".concat(yPosition.toString()).concat("' class='base'>").concat(trait).concat("</text>"))
427            yPosition = yPosition +  20
428            totalRarityScore = totalRarityScore + self.calculateRarityScore(itemName: trait)
429        }
430        
431        // Store traits details
432        self.traitsDetails[bagId] = Bag.TraitsDetails(background:background, body:body, cloth:cloth, glove:glove, helmet:helmet, ring:ring, weapon:weapon)
433        self.bagRarityScores[bagId] = totalRarityScore
434        
435        // Add rarity score
436        svgContent = svgContent.concat("<text x='10' y='250' class='base'>rarity score: ".concat(totalRarityScore.toString()).concat("</text>"))
437        svgContent = svgContent.concat("</svg>")
438        
439        return svgContent
440    }
441
442    access(self) fun generateTraitsKey(background: String, body: String, cloth: String, weapon: String, glove: String, ring: String, helmet: String): String {
443        return background.concat("|")
444            .concat(body).concat("|")
445            .concat(cloth).concat("|")
446            .concat(weapon).concat("|")
447            .concat(glove).concat("|")
448            .concat(ring).concat("|")
449            .concat(helmet)
450    }
451
452    access(self) fun createSVG(): String {
453        let svgImage = Bag.generateSVG()
454        let base64SVG = Base64Util.encode(svgImage)
455        return "data:image/svg+xml;base64,".concat(base64SVG)
456    }
457
458    access(all) fun mintNFT(user:Address, payment: @FlowToken.Vault): @Bag.NFT {
459        pre {
460            self.totalSupply < (self.maxSupply - self.reservedSupply): 
461                "Maximum supply reached. Total supply: ".concat(self.totalSupply.toString())
462            payment.balance >= self.mintPrice: 
463                "Insufficient payment. Required: ".concat(self.mintPrice.toString()).concat(", Provided: ").concat(payment.balance.toString())
464            self.getCollectionLength(user: user) < 20 : "Maximum 20 Bag per account"
465        }
466        
467        let contractReceiver = self.account.capabilities
468            .borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
469            ?? panic("Contract Flow token receiver capability not found")
470        
471        contractReceiver.deposit(from: <- payment)
472        
473        Bag.totalSupply = Bag.totalSupply + 1
474        let id = Bag.totalSupply
475        let svg = Bag.createSVG()
476
477        let rarityScore = self.bagRarityScores[id] ?? panic("Rarity score not found for NFT ID: ".concat(id.toString()))
478        
479        var newNFT <- create NFT(id: id, svg: svg, rarityScore: rarityScore)
480        emit NFTMinted(id: newNFT.id, svg: newNFT.svg, mintedFor:user)
481
482        if(Bag.totalSupply % Bag.priceUpdateInterval == 0 && Bag.totalSupply > 0){
483            self.updatePrice()
484        }
485
486        return <- newNFT
487    }
488
489    access(self) fun updatePrice() {
490        let oldPrice = Bag.mintPrice
491        Bag.mintPrice = Bag.mintPrice + Bag.priceIncreaseAmount
492        emit PriceUpdated(oldPrice: oldPrice, newPrice: Bag.mintPrice)
493    }
494
495    access(all) fun resolveNFTTraits(nftId: UInt64): MetadataViews.Traits? {
496        if let traits = self.traitsDetails[nftId] {
497            let metadata: {String: AnyStruct} = {
498                "Background": traits.background,
499                "Body": traits.body,
500                "Cloth": traits.cloth,
501                "Glove": traits.glove,
502                "Helmet": traits.helmet,
503                "Ring": traits.ring,
504                "Weapon": traits.weapon
505            }
506            return MetadataViews.dictToTraits(dict: metadata, excludedNames: [])
507        }
508        return nil
509    }
510
511    access(all) view fun getContractViews(resourceType: Type?): [Type] {
512        return [
513            Type<MetadataViews.NFTCollectionData>(),
514            Type<MetadataViews.NFTCollectionDisplay>(),
515            Type<MetadataViews.Royalties>()
516        ]
517    }
518    
519    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
520        switch viewType {
521            case Type<MetadataViews.NFTCollectionData>():
522                return MetadataViews.NFTCollectionData(
523                    storagePath: Bag.CollectionStoragePath,
524                    publicPath: Bag.CollectionPublicPath,
525                    publicCollection: Type<&Collection>(),
526                    publicLinkedType: Type<&Collection>(),
527                    createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
528                        return <-Bag.createEmptyCollection(nftType: Type<@Bag.NFT>())
529                    })
530                )
531            case Type<MetadataViews.NFTCollectionDisplay>():
532				let media = MetadataViews.Media(
533					file: MetadataViews.HTTPFile(
534						url: "https://white-worldwide-unicorn-392.mypinata.cloud/ipfs/bafybeigeadg24nqk5vuxjrcuv7w5p6ujxtzv3rilpigwh3c5wbmk2utyva"
535					),
536					mediaType: "image/png"
537				)
538				let mediaBanner = MetadataViews.Media(
539					file: MetadataViews.HTTPFile(
540						url: "https://white-worldwide-unicorn-392.mypinata.cloud/ipfs/bafybeihoztqdrbkkdmb3jmo7yrx4kdcamq7hr5ohsuq3ks3kmpfo3yrwxm"
541					),
542					mediaType: "image/png"
543				)
544				return MetadataViews.NFTCollectionDisplay(
545					name: "Bag Collection",
546					description: "Own a Bag & get the chance to win weekly rewards and more",
547					externalURL: MetadataViews.ExternalURL("https://onchainbag.xyz"),
548					squareImage: media,
549					bannerImage: mediaBanner,
550					socials: {"twitter": MetadataViews.ExternalURL("https://x.com/onchainbag")}
551				)
552            case Type<MetadataViews.Royalties>():
553                    return MetadataViews.Royalties([])
554                default:
555                    return nil
556        }
557    }
558
559    /* --- View Functions --- */
560    access(all) view fun getFlowBalance(): UFix64 {
561        let vaultRef = self.account.capabilities
562            .borrow<&FlowToken.Vault>(/public/flowTokenBalance)
563            ?? panic("Flow token vault not found at /public/flowTokenBalance")
564        
565        return vaultRef.balance
566    }
567
568    access(all) view fun getMintPrice(): UFix64 {
569        return self.mintPrice
570    }
571
572    access(all) view fun getTotalSupply(): UInt64 {
573        return self.totalSupply
574    }
575
576    access(all) view fun getMaxSupply(): UInt64 {
577        return self.maxSupply
578    }
579
580    access(all) view fun getReservedSupply(): UInt64 {
581        return self.reservedSupply
582    }
583
584    access(all) view fun getReservedMinted(): UInt64 {
585        return self.reservedMinted
586    }
587
588    access(all) view fun getTraitsDetails(nftId: UInt64): Bag.TraitsDetails? {
589        return self.traitsDetails[nftId]
590    }
591
592    access(all) view fun getBagRarityScore(nftId: UInt64): UInt64? {
593        return self.bagRarityScores[nftId]
594    }
595
596    access(all) view fun hasCollection(user: Address): Bool {
597        let account = getAccount(user)
598        return account.capabilities.get<&Bag.Collection>(Bag.CollectionPublicPath).check()
599    }
600
601    access(all) view fun getCollectionRef(user:Address): &Bag.Collection{
602        return getAccount(user).capabilities.get<&Bag.Collection>(Bag.CollectionPublicPath).borrow()?? panic("Cannot borrow collection reference")
603    }
604
605    access(all) view fun getCollectionLength(user: Address): Int {
606        pre {
607            self.hasCollection(user: user): "User does not have a Bag collection"
608        }
609
610        return self.getCollectionRef(user:user).getIDs().length
611    }
612
613    access(all) view fun getCollectionNFTIds(user: Address): [UInt64] {
614        pre {
615            self.hasCollection(user: user): 
616                "User does not have a Bag collection"
617        } 
618        
619        return self.getCollectionRef(user:user).getIDs()
620    }
621
622    access(all) fun getNFTWinCount(ownerAddress: Address, nftId: UInt64): UInt64 {
623        let nft = Bag.borrowNFT(ownerAddress: ownerAddress, nftId: nftId)
624        return nft.winCount
625    }
626
627    access(all) fun getNFTRarityScore(ownerAddress: Address, nftId: UInt64): UInt64 {
628        let nft = Bag.borrowNFT(ownerAddress: ownerAddress, nftId: nftId)
629        return nft.rarityScore
630    }
631
632    init(owner: Address, mintPrice:UFix64, reserveSupply:UInt64, registryAddress:Address) {
633        self.totalSupply = 0
634        self.maxSupply = 7777
635        self.mintPrice = mintPrice
636        self.traitsDetails = {}
637        self.bagRarityScores = {}
638        self.uniqueTraitsCombinations = []
639
640        self.owner = owner
641        self.registryAddress = registryAddress
642        self.reservedSupply = reserveSupply
643        self.reservedMinted = 0
644
645        // After each interval, mint price will increase by 10.0 Flow
646        // Giving benefit to early holders
647        self.priceUpdateInterval = 555
648        self.priceIncreaseAmount = 10.0
649
650        self.CollectionStoragePath = /storage/BagCollections
651        self.CollectionPublicPath = /public/BagCollectionPublics
652        self.CollectionPrivatePath = /private/BagCollectionProviders
653        self.AdminStoragePath = /storage/BagAdmins
654
655        let collection <- create Collection()
656        self.account.storage.save(<- collection, to: self.CollectionStoragePath)
657
658        let admin <- create Admin()
659        self.account.storage.save(<- admin, to: self.AdminStoragePath)
660
661        let collectionCapability = self.account.capabilities.storage
662            .issue<&Bag.Collection>(Bag.CollectionStoragePath)
663        self.account.capabilities.publish(collectionCapability, at: self.CollectionPublicPath)
664
665        emit ContractInitialized()
666    }
667}