Smart Contract

FishNFT

A.44100f14f70e3f78.FishNFT

Valid From

118,788,130

Deployed

5d ago
Feb 22, 2026, 03:29:10 PM UTC

Dependents

33 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import MetadataViews from 0x1d7e57aa55817448
3import Xorshift128plus from 0x45caec600164c9e6
4
5access(all) contract FishNFT: NonFungibleToken {
6
7    // DESIGN NOTES:
8    // This NFT contract implements strict immutability - once a catch is minted,
9    // none of its data can be modified. This ensures:
10    // 1. Data integrity of verified catches
11    // 2. Fair competition and prize distribution
12    // 3. Permanent historical record of catches
13    // 4. Trust in the DerbyFish ecosystem
14    //
15    // All catch data, including:
16    // - Core catch details
17    // - Competition results
18    // - Location data
19    // - Environmental conditions
20    // - Verification status
21    // Must be provided at mint time and cannot be updated.
22
23    // SPECIES COIN INTEGRATION - Simple registry approach
24    access(all) var speciesRegistry: {String: Address}  // speciesCode -> contract address
25    access(all) var totalFishCaught: UInt64
26
27    // Storage paths
28    access(all) let CollectionStoragePath: StoragePath
29    access(all) let CollectionPublicPath: PublicPath
30    access(all) let MinterStoragePath: StoragePath
31    
32    // FishCard storage paths
33    access(all) let FishCardCollectionStoragePath: StoragePath
34    access(all) let FishCardCollectionPublicPath: PublicPath
35    access(all) let FishCardMinterStoragePath: StoragePath
36
37    // Events
38    access(all) event SpeciesRegistered(speciesCode: String, contractAddress: Address)
39
40    access(all) event FishMinted(
41        id: UInt64,
42        recipient: Address,
43        species: String,
44        scientific: String,
45        length: UFix64,
46        latitude: Fix64,
47        longitude: Fix64,
48        timestamp: UFix64,
49        speciesCode: String
50    )
51
52    // FishCard events
53    access(all) event FishCardMinted(
54        id: UInt64,
55        fishNFTId: UInt64,
56        recipient: Address,
57        species: String,
58        revealedFields: [String]
59    )
60
61    access(all) event FishCardCommitted(
62        commitId: UInt64,
63        fishNFTId: UInt64,
64        committer: Address,
65        commitBlock: UInt64,
66        revealBlock: UInt64
67    )
68
69    access(all) event FishCardRevealed(
70        commitId: UInt64,
71        fishCardId: UInt64,
72        revealedFields: [String]
73    )
74
75    // FishCard state
76    access(all) var totalFishCards: UInt64
77    access(all) var nextCommitId: UInt64
78
79    // Active commits for commit-reveal scheme
80    access(self) var activeCommits: {UInt64: FishCardCommit}
81
82    // FishCard Receipt storage path
83    access(all) let FishCardReceiptStoragePath: StoragePath
84
85    // Simple commit structure
86    access(all) struct FishCardCommit {
87        access(all) let id: UInt64
88        access(all) let fishNFTId: UInt64
89        access(all) let fishNFTOwner: Address
90        access(all) let recipient: Address
91        access(all) let commitBlock: UInt64
92        access(all) let userSalt: [UInt8]
93
94        init(id: UInt64, fishNFTId: UInt64, fishNFTOwner: Address, recipient: Address, userSalt: [UInt8]) {
95            self.id = id
96            self.fishNFTId = fishNFTId
97            self.fishNFTOwner = fishNFTOwner
98            self.recipient = recipient
99            self.commitBlock = getCurrentBlock().height
100            self.userSalt = userSalt
101        }
102    }
103
104    access(all) struct FishMetadata {
105        // PUBLIC CORE DATA - Always visible to everyone
106        access(all) let owner: Address              // Owner of the NFT
107        access(all) let species: String              // Common name
108        access(all) let scientific: String           // Scientific name
109        access(all) let length: UFix64               // Length in inches
110        access(all) let weight: UFix64?              // Weight in pounds if measured
111        access(all) let timestamp: UFix64            // When caught
112        access(all) let speciesCode: String          // Required species code for minting
113        access(all) let hasRelease: Bool             // Whether fish was released
114        access(all) let qualityScore: UFix64?        // DerbyFish quality score (1-100)
115        access(all) let waterBody: String?           // Lake, river, ocean name from location API
116        access(all) var allowFishCards: Bool         // Whether this fish can be used to mint FishCards (one-way switch)
117        
118        // COMPETITION DATA - Public competition results
119        access(all) let competitions: {UInt64: {String: UInt64}}  // Map of derbyId -> {leaderboardId: placement}
120        access(all) let prizesWon: [String]                      // List of prizes won with this catch
121        access(all) let totalPrizeValue: UFix64?                 // Total value of prizes won (if applicable)
122
123        // DERBYFISH SANCTIONING BODY DATA - Added during verification
124        access(all) let verificationLevel: String    // Level of verification (basic, enhanced, tournament)
125        access(all) let verifiedBy: Address          // Address of verifier
126        access(all) let verifiedAt: UFix64           // When verified
127        access(all) let competitionId: String?       // If caught during competition
128        access(all) let recordStatus: String?        // If this is a record catch (state, lake, etc)
129        access(all) let certificationLevel: String?  // Certification level of the catch
130
131        // REQUIRED MEDIA - For verification
132        access(all) let bumpShotUrl: String          // Bump board measurement photo
133        access(all) let heroShotUrl: String          // Beauty shot of catch
134        access(all) let bumpHash: String             // Hash of bump shot
135        access(all) let heroHash: String             // Hash of hero shot
136        access(all) let releaseVideoUrl: String?     // Optional release video URL
137        access(all) let releaseHash: String?         // Release video hash if exists
138
139        // PRIVATE LOCATION DATA - Only visible to owner and contract admin
140        access(contract) let longitude: Fix64             // Exact longitude from GPS
141        access(contract) let latitude: Fix64              // Exact latitude from GPS
142        access(contract) let waterTemp: UFix64?           // Temperature in Fahrenheit from weather API
143        access(contract) let airTemp: UFix64?             // Temperature in Fahrenheit from weather API
144        access(contract) let weather: String?             // Weather conditions from API
145        access(contract) let moonPhase: String?           // Moon phase calculated by app
146        access(contract) let tide: String?                // Tide data if coastal location
147        access(contract) let barometricPressure: UFix64?  // Barometric pressure from weather API
148        access(contract) let windSpeed: UFix64?           // Wind speed from weather API
149        access(contract) let windDirection: String?       // Wind direction from weather API
150        access(contract) let skyConditions: String?       // Weather conditions from API
151        access(contract) let waterDepth: UFix64?          // Depth from bathymetric data if available
152        access(contract) let structureType: String?       // Structure type from location data
153        access(contract) let bottomType: String?          // Bottom composition from location data
154
155        // PRIVATE ANGLER DATA - Only visible to owner and contract admin
156        access(contract) let location: String?            // Named spot/location
157        access(contract) let waterClarity: String?        // Water visibility/clarity
158        access(contract) let currentStrength: String?     // Water current strength
159        access(contract) let gear: String?                // Equipment used
160        access(contract) let baitLure: String?            // Specific bait or lure
161        access(contract) let fightDuration: UFix64?       // Fight duration in seconds
162        access(contract) let technique: String?           // Fishing technique used
163        access(contract) let girth: UFix64?               // Girth in inches if measured
164        access(contract) let rodType: String?             // Type of rod used
165        access(contract) let reelType: String?            // Type of reel used
166        access(contract) let lineType: String?            // Type of line used
167        access(contract) let leaderType: String?          // Type of leader used
168        access(contract) let hookType: String?            // Type of hook used
169        access(contract) let presentation: String?        // How the bait/lure was presented
170        access(contract) let retrieveSpeed: String?       // Speed of retrieve
171        access(contract) let catchDepth: UFix64?          // Depth fish was hooked at
172
173        // Function to get private data if caller is authorized
174        access(all) fun getPrivateData(caller: Address): {String: AnyStruct}? {
175            // Only allow the NFT owner or contract admin to access private data
176            if caller != self.owner && caller != FishNFT.account.address {
177                return nil
178            }
179
180            return {
181                // Location data
182                "longitude": self.longitude,
183                "latitude": self.latitude,
184                "waterTemp": self.waterTemp,
185                "airTemp": self.airTemp,
186                "weather": self.weather,
187                "moonPhase": self.moonPhase,
188                "tide": self.tide,
189                "barometricPressure": self.barometricPressure,
190                "windSpeed": self.windSpeed,
191                "windDirection": self.windDirection,
192                "skyConditions": self.skyConditions,
193                "waterDepth": self.waterDepth,
194                "structureType": self.structureType,
195                "bottomType": self.bottomType,
196
197                // Angler data
198                "location": self.location,
199                "waterClarity": self.waterClarity,
200                "currentStrength": self.currentStrength,
201                "gear": self.gear,
202                "baitLure": self.baitLure,
203                "fightDuration": self.fightDuration,
204                "technique": self.technique,
205                "girth": self.girth,
206                "rodType": self.rodType,
207                "reelType": self.reelType,
208                "lineType": self.lineType,
209                "leaderType": self.leaderType,
210                "hookType": self.hookType,
211                "presentation": self.presentation,
212                "retrieveSpeed": self.retrieveSpeed,
213                "catchDepth": self.catchDepth
214            }
215        }
216
217        // Function to enable fish card minting - can only be called once
218        access(all) fun enableFishCards() {
219            pre {
220                !self.allowFishCards: "Fish card minting has already been enabled"
221            }
222            self.allowFishCards = true
223        }
224
225        init(
226            // PUBLIC CORE DATA
227            owner: Address,
228            species: String,
229            scientific: String,
230            length: UFix64,
231            weight: UFix64?,
232            timestamp: UFix64,
233            speciesCode: String,
234            hasRelease: Bool,
235
236            // DERBY & COMPETITION DATA
237            competitions: {UInt64: {String: UInt64}},
238            prizesWon: [String],
239            totalPrizeValue: UFix64?,
240            
241            // DERBYFISH SANCTIONING BODY DATA
242            verificationLevel: String,
243            verifiedBy: Address,
244            verifiedAt: UFix64,
245            competitionId: String?,
246            recordStatus: String?,
247            certificationLevel: String?,
248            qualityScore: UFix64?,
249
250            // LOCATION & ENVIRONMENTAL DATA
251            longitude: Fix64,
252            latitude: Fix64,
253            waterTemp: UFix64?,
254            airTemp: UFix64?,
255            weather: String?,
256            moonPhase: String?,
257            tide: String?,
258            waterBody: String?,
259            barometricPressure: UFix64?,
260            windSpeed: UFix64?,
261            windDirection: String?,
262            skyConditions: String?,
263            waterDepth: UFix64?,
264            structureType: String?,
265            bottomType: String?,
266
267            // MEDIA
268            bumpShotUrl: String,
269            heroShotUrl: String,
270            bumpHash: String,
271            heroHash: String,
272            releaseVideoUrl: String?,
273            releaseHash: String?,
274
275            // ANGLER DATA
276            location: String?,
277            waterClarity: String?,
278            currentStrength: String?,
279            gear: String?,
280            baitLure: String?,
281            fightDuration: UFix64?,
282            technique: String?,
283            girth: UFix64?,
284            rodType: String?,
285            reelType: String?,
286            lineType: String?,
287            leaderType: String?,
288            hookType: String?,
289            presentation: String?,
290            retrieveSpeed: String?,
291            catchDepth: UFix64?
292        ) {
293            self.owner = owner
294            self.species = species
295            self.scientific = scientific
296            self.length = length
297            self.weight = weight
298            self.timestamp = timestamp
299            self.speciesCode = speciesCode
300            self.hasRelease = hasRelease
301            self.allowFishCards = false  // Disabled by default
302
303            // Competition data
304            self.competitions = competitions
305            self.prizesWon = prizesWon
306            self.totalPrizeValue = totalPrizeValue
307
308            // Verification data
309            self.verificationLevel = verificationLevel
310            self.verifiedBy = verifiedBy
311            self.verifiedAt = verifiedAt
312            self.competitionId = competitionId
313            self.recordStatus = recordStatus
314            self.certificationLevel = certificationLevel
315            self.qualityScore = qualityScore
316
317            // Location data
318            self.longitude = longitude
319            self.latitude = latitude
320            self.waterTemp = waterTemp
321            self.airTemp = airTemp
322            self.weather = weather
323            self.moonPhase = moonPhase
324            self.tide = tide
325            self.waterBody = waterBody
326            self.barometricPressure = barometricPressure
327            self.windSpeed = windSpeed
328            self.windDirection = windDirection
329            self.skyConditions = skyConditions
330            self.waterDepth = waterDepth
331            self.structureType = structureType
332            self.bottomType = bottomType
333
334            // Media
335            self.bumpShotUrl = bumpShotUrl
336            self.heroShotUrl = heroShotUrl
337            self.bumpHash = bumpHash
338            self.heroHash = heroHash
339            self.releaseVideoUrl = releaseVideoUrl
340            self.releaseHash = releaseHash
341
342            // Angler data
343            self.location = location
344            self.waterClarity = waterClarity
345            self.currentStrength = currentStrength
346            self.gear = gear
347            self.baitLure = baitLure
348            self.fightDuration = fightDuration
349            self.technique = technique
350            self.girth = girth
351            self.rodType = rodType
352            self.reelType = reelType
353            self.lineType = lineType
354            self.leaderType = leaderType
355            self.hookType = hookType
356            self.presentation = presentation
357            self.retrieveSpeed = retrieveSpeed
358            self.catchDepth = catchDepth
359        }
360    }
361
362    // FishCard metadata structure - contains selectively revealed data from Fish NFT
363    access(all) struct FishCardMetadata {
364        // CORE FIELDS - Always included from original Fish NFT
365        access(all) let fishNFTId: UInt64
366        access(all) let originalOwner: Address
367        access(all) let species: String
368        access(all) let scientific: String
369        access(all) let length: UFix64
370        access(all) let timestamp: UFix64
371        access(all) let speciesCode: String
372        access(all) let hasRelease: Bool
373        
374        // SELECTIVELY REVEALED FIELDS - Based on VRF coin flips
375        access(all) let weight: UFix64?
376        access(all) let qualityScore: UFix64?
377        access(all) let waterBody: String?
378        access(all) let verificationLevel: String?
379        access(all) let bumpShotUrl: String?
380        access(all) let heroShotUrl: String?
381        
382        // REVEALED LOCATION DATA - Based on coin flips
383        access(all) let longitude: Fix64?
384        access(all) let latitude: Fix64?
385        access(all) let waterTemp: UFix64?
386        access(all) let airTemp: UFix64?
387        access(all) let weather: String?
388        access(all) let moonPhase: String?
389        access(all) let tide: String?
390        
391        // REVEALED ANGLER DATA - Based on coin flips  
392        access(all) let location: String?
393        access(all) let gear: String?
394        access(all) let baitLure: String?
395        access(all) let technique: String?
396        access(all) let girth: UFix64?
397        access(all) let fightDuration: UFix64?
398        
399        // METADATA
400        access(all) let revealedFields: [String]
401        access(all) let cardRarity: String  // Based on number of revealed fields
402        
403        init(
404            fishNFTId: UInt64,
405            originalOwner: Address,
406            species: String,
407            scientific: String,
408            length: UFix64,
409            timestamp: UFix64,
410            speciesCode: String,
411            hasRelease: Bool,
412            weight: UFix64?,
413            qualityScore: UFix64?,
414            waterBody: String?,
415            verificationLevel: String?,
416            bumpShotUrl: String?,
417            heroShotUrl: String?,
418            longitude: Fix64?,
419            latitude: Fix64?,
420            waterTemp: UFix64?,
421            airTemp: UFix64?,
422            weather: String?,
423            moonPhase: String?,
424            tide: String?,
425            location: String?,
426            gear: String?,
427            baitLure: String?,
428            technique: String?,
429            girth: UFix64?,
430            fightDuration: UFix64?,
431            revealedFields: [String]
432        ) {
433            self.fishNFTId = fishNFTId
434            self.originalOwner = originalOwner
435            self.species = species
436            self.scientific = scientific
437            self.length = length
438            self.timestamp = timestamp
439            self.speciesCode = speciesCode
440            self.hasRelease = hasRelease
441            self.weight = weight
442            self.qualityScore = qualityScore
443            self.waterBody = waterBody
444            self.verificationLevel = verificationLevel
445            self.bumpShotUrl = bumpShotUrl
446            self.heroShotUrl = heroShotUrl
447            self.longitude = longitude
448            self.latitude = latitude
449            self.waterTemp = waterTemp
450            self.airTemp = airTemp
451            self.weather = weather
452            self.moonPhase = moonPhase
453            self.tide = tide
454            self.location = location
455            self.gear = gear
456            self.baitLure = baitLure
457            self.technique = technique
458            self.girth = girth
459            self.fightDuration = fightDuration
460            self.revealedFields = revealedFields
461            
462            // Calculate rarity based on revealed fields
463            let revealCount = revealedFields.length
464            if revealCount <= 3 {
465                self.cardRarity = "Common"
466            } else if revealCount <= 6 {
467                self.cardRarity = "Uncommon"
468            } else if revealCount <= 9 {
469                self.cardRarity = "Rare"
470            } else if revealCount <= 12 {
471                self.cardRarity = "Epic"
472            } else {
473                self.cardRarity = "Legendary"
474            }
475        }
476    }
477
478    access(all) resource NFT: NonFungibleToken.NFT {
479        access(all) let id: UInt64
480        access(all) let metadata: FishMetadata
481        access(all) let mintedBy: Address
482
483        // Convenience accessors for common fields
484        access(all) view fun getSpecies(): String {
485            return self.metadata.species
486        }
487
488        access(all) view fun getSpeciesCode(): String {
489            return self.metadata.speciesCode
490        }
491
492        access(all) view fun getLength(): UFix64 {
493            return self.metadata.length
494        }
495
496        access(all) view fun getWeight(): UFix64? {
497            return self.metadata.weight
498        }
499
500        access(all) view fun getTimestamp(): UFix64 {
501            return self.metadata.timestamp
502        }
503
504        access(all) view fun getHasRelease(): Bool {
505            return self.metadata.hasRelease
506        }
507
508        access(all) view fun getOwner(): Address {
509            return self.metadata.owner
510        }
511
512        // Access private data (only for owner or admin)
513        access(all) fun getPrivateData(caller: Address): {String: AnyStruct}? {
514            return self.metadata.getPrivateData(caller: caller)
515        }
516
517        // Enable fish card minting
518        access(all) fun enableFishCards() {
519            self.metadata.enableFishCards()
520        }
521
522        access(all) view fun canMintFishCards(): Bool {
523            return self.metadata.allowFishCards
524        }
525
526        // Views implementation
527        access(all) view fun getViews(): [Type] {
528            return [
529                Type<MetadataViews.Display>(),
530                Type<MetadataViews.Royalties>(),
531                Type<MetadataViews.ExternalURL>(),
532                Type<MetadataViews.NFTCollectionData>(),
533                Type<MetadataViews.NFTCollectionDisplay>(),
534                Type<MetadataViews.Serial>(),
535                Type<MetadataViews.Traits>()
536            ]
537        }
538
539        access(all) fun resolveView(_ view: Type): AnyStruct? {
540            switch view {
541                case Type<MetadataViews.Display>():
542                    return MetadataViews.Display(
543                        name: self.metadata.species.concat(" - ").concat(self.metadata.length.toString()).concat("\""),
544                        description: "A verified ".concat(self.metadata.species).concat(" catch from DerbyFish"),
545                        thumbnail: MetadataViews.HTTPFile(
546                            url: self.metadata.heroShotUrl
547                        )
548                    )
549                case Type<MetadataViews.Serial>():
550                    return MetadataViews.Serial(
551                        self.id
552                    )
553                case Type<MetadataViews.Royalties>():
554                    return MetadataViews.Royalties([])
555                case Type<MetadataViews.ExternalURL>():
556                    return MetadataViews.ExternalURL("https://derby.fish/catch/".concat(self.id.toString()))
557                case Type<MetadataViews.NFTCollectionData>():
558                    return MetadataViews.NFTCollectionData(
559                        storagePath: FishNFT.CollectionStoragePath,
560                        publicPath: FishNFT.CollectionPublicPath,
561                        publicCollection: Type<&FishNFT.Collection>(),
562                        publicLinkedType: Type<&FishNFT.Collection>(),
563                        createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
564                            return <-FishNFT.createEmptyCollection(nftType: Type<@FishNFT.NFT>())
565                        })
566                    )
567                case Type<MetadataViews.NFTCollectionDisplay>():
568                    return MetadataViews.NFTCollectionDisplay(
569                        name: "DerbyFish Catches",
570                        description: "Verified fishing catches in the DerbyFish ecosystem",
571                        externalURL: MetadataViews.ExternalURL("https://derby.fish"),
572                        squareImage: MetadataViews.Media(
573                            file: MetadataViews.HTTPFile(url: "https://derby.fish/images/logo-square.png"),
574                            mediaType: "image/png"
575                        ),
576                        bannerImage: MetadataViews.Media(
577                            file: MetadataViews.HTTPFile(url: "https://derby.fish/images/banner.png"),
578                            mediaType: "image/png"
579                        ),
580                        socials: {
581                            "website": MetadataViews.ExternalURL("https://derby.fish"),
582                            "twitter": MetadataViews.ExternalURL("https://twitter.com/derby_fish")
583                        }
584                    )
585                case Type<MetadataViews.Traits>():
586                    return MetadataViews.Traits([
587                        MetadataViews.Trait(name: "species", value: self.metadata.species, displayType: nil, rarity: nil),
588                        MetadataViews.Trait(name: "scientific", value: self.metadata.scientific, displayType: nil, rarity: nil),
589                        MetadataViews.Trait(name: "length", value: self.metadata.length, displayType: "Number", rarity: nil),
590                        MetadataViews.Trait(name: "hasRelease", value: self.metadata.hasRelease, displayType: nil, rarity: nil),
591                        MetadataViews.Trait(name: "speciesCode", value: self.metadata.speciesCode, displayType: nil, rarity: nil)
592                    ])
593            }
594            return nil
595        }
596
597        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
598            return <-create Collection()
599        }
600
601        init(id: UInt64, metadata: FishMetadata, mintedBy: Address) {
602            self.id = id
603            self.metadata = metadata
604            self.mintedBy = mintedBy
605        }
606    }
607
608    // FishCard NFT Resource
609    access(all) resource FishCard: NonFungibleToken.NFT {
610        access(all) let id: UInt64
611        access(all) let metadata: FishCardMetadata
612        access(all) let mintedBy: Address
613        access(all) let mintedAt: UFix64
614
615        // Convenience accessors
616        access(all) view fun getSpecies(): String {
617            return self.metadata.species
618        }
619
620        access(all) view fun getFishNFTId(): UInt64 {
621            return self.metadata.fishNFTId
622        }
623
624        access(all) view fun getRarity(): String {
625            return self.metadata.cardRarity
626        }
627
628        access(all) view fun getRevealedFields(): [String] {
629            return self.metadata.revealedFields
630        }
631
632        // Views implementation for FishCard
633        access(all) view fun getViews(): [Type] {
634            return [
635                Type<MetadataViews.Display>(),
636                Type<MetadataViews.Serial>(),
637                Type<MetadataViews.Traits>()
638            ]
639        }
640
641        access(all) fun resolveView(_ view: Type): AnyStruct? {
642            switch view {
643                case Type<MetadataViews.Display>():
644                    return MetadataViews.Display(
645                        name: self.metadata.species.concat(" Card #").concat(self.id.toString()),
646                        description: "A trading card featuring a ".concat(self.metadata.species).concat(" catch - ").concat(self.metadata.cardRarity).concat(" rarity"),
647                        thumbnail: MetadataViews.HTTPFile(
648                            url: self.metadata.heroShotUrl ?? "https://derby.fish/images/card-placeholder.png"
649                        )
650                    )
651                case Type<MetadataViews.Serial>():
652                    return MetadataViews.Serial(self.id)
653                case Type<MetadataViews.Traits>():
654                    let traits: [MetadataViews.Trait] = []
655                    traits.append(MetadataViews.Trait(name: "species", value: self.metadata.species, displayType: nil, rarity: nil))
656                    traits.append(MetadataViews.Trait(name: "rarity", value: self.metadata.cardRarity, displayType: nil, rarity: nil))
657                    traits.append(MetadataViews.Trait(name: "revealedFields", value: self.metadata.revealedFields.length, displayType: "Number", rarity: nil))
658                    traits.append(MetadataViews.Trait(name: "fishNFTId", value: self.metadata.fishNFTId, displayType: "Number", rarity: nil))
659                    return MetadataViews.Traits(traits)
660            }
661            return nil
662        }
663
664        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
665            return <-create FishCardCollection()
666        }
667
668        init(id: UInt64, metadata: FishCardMetadata, mintedBy: Address) {
669            self.id = id
670            self.metadata = metadata
671            self.mintedBy = mintedBy
672            self.mintedAt = getCurrentBlock().timestamp
673        }
674    }
675
676    access(all) resource Collection: NonFungibleToken.Collection {
677        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
678
679        access(all) view fun getLength(): Int {
680            return self.ownedNFTs.length
681        }
682
683        access(all) view fun getIDs(): [UInt64] {
684            return self.ownedNFTs.keys
685        }
686
687        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
688            return &self.ownedNFTs[id]
689        }
690
691        access(all) fun borrowEntireNFT(id: UInt64): &FishNFT.NFT? {
692            if self.ownedNFTs[id] != nil {
693                let ref = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}?
694                return ref as! &FishNFT.NFT
695            }
696            return nil
697        }
698
699        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
700            let token <- token as! @FishNFT.NFT
701            let id = token.id
702            let oldToken <- self.ownedNFTs[id] <- token
703            destroy oldToken
704        }
705
706        // Override withdraw to prevent transfers
707        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
708            panic("FishNFTs are non-transferable")
709        }
710
711        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
712            let supportedTypes: {Type: Bool} = {}
713            supportedTypes[Type<@FishNFT.NFT>()] = true
714            return supportedTypes
715        }
716
717        access(all) view fun isSupportedNFTType(type: Type): Bool {
718            return type == Type<@FishNFT.NFT>()
719        }
720
721        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
722            return <-create Collection()
723        }
724
725        init() {
726            self.ownedNFTs <- {}
727        }
728    }
729
730    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
731        return <- create Collection()
732    }
733
734    // FishCard Collection Resource - Remains transferable
735    access(all) resource FishCardCollection: NonFungibleToken.Collection {
736        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
737
738        access(all) view fun getLength(): Int {
739            return self.ownedNFTs.length
740        }
741
742        access(all) view fun getIDs(): [UInt64] {
743            return self.ownedNFTs.keys
744        }
745
746        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
747            return &self.ownedNFTs[id]
748        }
749
750        access(all) fun borrowFishCard(id: UInt64): &FishNFT.FishCard? {
751            if self.ownedNFTs[id] != nil {
752                let ref = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}?
753                return ref as! &FishNFT.FishCard
754            }
755            return nil
756        }
757
758        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
759            let token <- token as! @FishNFT.FishCard
760            let id = token.id
761            let oldToken <- self.ownedNFTs[id] <- token
762            destroy oldToken
763        }
764
765        // Keep withdraw enabled for FishCards
766        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
767            let token <- self.ownedNFTs.remove(key: withdrawID)
768                ?? panic("Could not withdraw a FishCard with the specified ID")
769            return <-token
770        }
771
772        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
773            let supportedTypes: {Type: Bool} = {}
774            supportedTypes[Type<@FishNFT.FishCard>()] = true
775            return supportedTypes
776        }
777
778        access(all) view fun isSupportedNFTType(type: Type): Bool {
779            return type == Type<@FishNFT.FishCard>()
780        }
781
782        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
783            return <-create FishCardCollection()
784        }
785
786        init() {
787            self.ownedNFTs <- {}
788        }
789    }
790
791    access(all) fun createEmptyFishCardCollection(): @{NonFungibleToken.Collection} {
792        return <- create FishCardCollection()
793    }
794
795    // Simple FishCard Receipt for commit-reveal scheme
796    access(all) resource FishCardReceipt {
797        access(all) let commitId: UInt64
798
799        init(commitId: UInt64) {
800            self.commitId = commitId
801        }
802    }
803
804    access(all) resource NFTMinter {
805        access(all) var nextID: UInt64
806
807        init() {
808            self.nextID = 1
809        }
810
811        access(all) fun mintNFT(
812            recipient: Address,
813            metadata: FishMetadata
814        ): @FishNFT.NFT {
815            let newNFT <- create FishNFT.NFT(
816                id: self.nextID,
817                metadata: metadata,
818                mintedBy: FishNFT.account.address
819            )
820
821            emit FishMinted(
822                id: self.nextID,
823                recipient: recipient,
824                species: metadata.species,
825                scientific: metadata.scientific,
826                length: metadata.length,
827                latitude: metadata.latitude,
828                longitude: metadata.longitude,
829                timestamp: metadata.timestamp,
830                speciesCode: metadata.speciesCode
831            )
832
833            // Update total fish caught counter
834            FishNFT.totalFishCaught = FishNFT.totalFishCaught + 1
835
836            self.nextID = self.nextID + 1
837
838            return <-newNFT
839        }
840        
841        // Enhanced mint function with species validation
842        access(all) fun mintNFTWithSpeciesValidation(
843            // REQUIRED CORE DATA
844            recipient: Address,
845            species: String,
846            scientific: String,
847            length: UFix64,
848            weight: UFix64?,
849            timestamp: UFix64,
850            speciesCode: String,
851            hasRelease: Bool,
852            
853            // REQUIRED MEDIA
854            bumpShotUrl: String,
855            heroShotUrl: String,
856            bumpHash: String,
857            heroHash: String,
858            releaseVideoUrl: String?,
859            releaseHash: String?,
860            
861            // LOCATION DATA
862            longitude: Fix64,
863            latitude: Fix64,
864            waterBody: String?,
865            
866            // OPTIONAL ENVIRONMENTAL DATA
867            waterTemp: UFix64?,
868            airTemp: UFix64?,
869            weather: String?,
870            moonPhase: String?,
871            tide: String?,
872            barometricPressure: UFix64?,
873            windSpeed: UFix64?,
874            windDirection: String?,
875            skyConditions: String?,
876            waterDepth: UFix64?,
877            structureType: String?,
878            bottomType: String?,
879            
880            // OPTIONAL ANGLER DATA
881            location: String?,
882            waterClarity: String?,
883            currentStrength: String?,
884            gear: String?,
885            baitLure: String?,
886            fightDuration: UFix64?,
887            technique: String?,
888            girth: UFix64?,
889            rodType: String?,
890            reelType: String?,
891            lineType: String?,
892            leaderType: String?,
893            hookType: String?,
894            presentation: String?,
895            retrieveSpeed: String?,
896            catchDepth: UFix64?
897        ): @FishNFT.NFT {
898            
899            // Create metadata with all provided fields
900            let metadata = FishMetadata(
901                // PUBLIC CORE DATA
902                owner: recipient,
903                species: species,
904                scientific: scientific,
905                length: length,
906                weight: weight,
907                timestamp: timestamp,
908                speciesCode: speciesCode,
909                hasRelease: hasRelease,
910
911                // COMPETITION DATA - Empty initially
912                competitions: {},
913                prizesWon: [],
914                totalPrizeValue: nil,
915                
916                // VERIFICATION DATA - Basic verification initially
917                verificationLevel: "basic",
918                verifiedBy: FishNFT.account.address,
919                verifiedAt: getCurrentBlock().timestamp,
920                competitionId: nil,
921                recordStatus: nil,
922                certificationLevel: nil,
923                qualityScore: nil,
924
925                // LOCATION & ENVIRONMENTAL DATA
926                longitude: longitude,
927                latitude: latitude,
928                waterTemp: waterTemp,
929                airTemp: airTemp,
930                weather: weather,
931                moonPhase: moonPhase,
932                tide: tide,
933                waterBody: waterBody,
934                barometricPressure: barometricPressure,
935                windSpeed: windSpeed,
936                windDirection: windDirection,
937                skyConditions: skyConditions,
938                waterDepth: waterDepth,
939                structureType: structureType,
940                bottomType: bottomType,
941
942                // MEDIA
943                bumpShotUrl: bumpShotUrl,
944                heroShotUrl: heroShotUrl,
945                bumpHash: bumpHash,
946                heroHash: heroHash,
947                releaseVideoUrl: releaseVideoUrl,
948                releaseHash: releaseHash,
949
950                // ANGLER DATA
951                location: location,
952                waterClarity: waterClarity,
953                currentStrength: currentStrength,
954                gear: gear,
955                baitLure: baitLure,
956                fightDuration: fightDuration,
957                technique: technique,
958                girth: girth,
959                rodType: rodType,
960                reelType: reelType,
961                lineType: lineType,
962                leaderType: leaderType,
963                hookType: hookType,
964                presentation: presentation,
965                retrieveSpeed: retrieveSpeed,
966                catchDepth: catchDepth
967            )
968            
969            return <- self.mintNFT(recipient: recipient, metadata: metadata)
970        }
971
972        access(all) fun mintNFTToCollection(
973            recipient: &{NonFungibleToken.Collection},
974            metadata: FishMetadata
975        ) {
976            let nft <- self.mintNFT(
977                recipient: recipient.owner?.address ?? FishNFT.account.address,
978                metadata: metadata
979            )
980            recipient.deposit(token: <-nft)
981        }
982    }
983
984    // FishCard Minter Resource with RandomConsumer commit-reveal pattern
985    access(all) resource FishCardMinter {
986        access(all) var nextCardID: UInt64
987
988        init() {
989            self.nextCardID = 1
990        }
991
992        // COMMIT PHASE: Create receipt for future FishCard minting
993        access(all) fun commitFishCard(
994            fishNFTId: UInt64,
995            fishNFTOwner: Address,
996            recipient: Address,
997            userSalt: [UInt8]
998        ): @FishCardReceipt {
999            // Verify Fish NFT exists and can mint cards
1000            let ownerAccount = getAccount(fishNFTOwner)
1001            let collectionRef = ownerAccount.capabilities.borrow<&FishNFT.Collection>(FishNFT.CollectionPublicPath)
1002                ?? panic("Could not borrow Fish NFT collection from owner")
1003            
1004            let fishNFT = collectionRef.borrowEntireNFT(id: fishNFTId)
1005                ?? panic("Could not borrow Fish NFT with ID: ".concat(fishNFTId.toString()))
1006
1007            assert(fishNFT.canMintFishCards(), message: "FishCard minting not enabled for this Fish NFT")
1008
1009            // Create commit
1010            let commitId = FishNFT.nextCommitId
1011            let commit = FishCardCommit(
1012                id: commitId,
1013                fishNFTId: fishNFTId,
1014                fishNFTOwner: fishNFTOwner,
1015                recipient: recipient,
1016                userSalt: userSalt
1017            )
1018
1019            // Store the commit
1020            FishNFT.activeCommits[commitId] = commit
1021            FishNFT.nextCommitId = FishNFT.nextCommitId + 1
1022
1023            // Create and return receipt
1024            let receipt <- create FishCardReceipt(commitId: commitId)
1025
1026            emit FishCardCommitted(
1027                commitId: commitId,
1028                fishNFTId: fishNFTId,
1029                committer: recipient,
1030                commitBlock: commit.commitBlock,
1031                revealBlock: commit.commitBlock + 1
1032            )
1033
1034            return <-receipt
1035        }
1036
1037        // REVEAL PHASE: Use committed randomness to mint FishCard
1038        access(all) fun revealFishCard(receipt: @FishCardReceipt): @FishNFT.FishCard {
1039            // Get commit data
1040            let commit = FishNFT.activeCommits[receipt.commitId]
1041                ?? panic("Commit not found")
1042
1043            assert(getCurrentBlock().height > commit.commitBlock, message: "Must wait at least 1 block to reveal")
1044
1045            // Get Fish NFT
1046            let ownerAccount = getAccount(commit.fishNFTOwner)
1047            let collectionRef = ownerAccount.capabilities.borrow<&FishNFT.Collection>(FishNFT.CollectionPublicPath)
1048                ?? panic("Could not borrow Fish NFT collection from owner")
1049            
1050            let fishNFT = collectionRef.borrowEntireNFT(id: commit.fishNFTId)
1051                ?? panic("Could not borrow Fish NFT")
1052
1053            // Get private data from Fish NFT
1054            let privateData = fishNFT.getPrivateData(caller: FishNFT.account.address)
1055            let fishMetadata = fishNFT.metadata
1056
1057            // Create randomness source using current block + user salt
1058            let blockHash = getCurrentBlock().id
1059            var blockHashArray: [UInt8] = []
1060            var i = 0
1061            while i < blockHash.length {
1062                blockHashArray.append(blockHash[i])
1063                i = i + 1
1064            }
1065            let randomSource = commit.userSalt.concat(blockHashArray)
1066            var prg = Xorshift128plus.PRG(sourceOfRandomness: randomSource, salt: commit.fishNFTId.toBigEndianBytes())
1067            
1068            // Perform coin flips for each non-core field using PRG
1069            let revealedFields: [String] = []
1070            
1071            // Non-core fields to coin flip
1072            let weight = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) ? fishMetadata.weight : nil
1073            if weight != nil { revealedFields.append("weight") }
1074            
1075            let qualityScore = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) ? fishMetadata.qualityScore : nil
1076            if qualityScore != nil { revealedFields.append("qualityScore") }
1077            
1078            let waterBody = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) ? fishMetadata.waterBody : nil
1079            if waterBody != nil { revealedFields.append("waterBody") }
1080            
1081            let verificationLevel = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) ? fishMetadata.verificationLevel : nil
1082            if verificationLevel != nil { revealedFields.append("verificationLevel") }
1083            
1084            let bumpShotUrl = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) ? fishMetadata.bumpShotUrl : nil
1085            if bumpShotUrl != nil { revealedFields.append("bumpShotUrl") }
1086            
1087            let heroShotUrl = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) ? fishMetadata.heroShotUrl : nil
1088            if heroShotUrl != nil { revealedFields.append("heroShotUrl") }
1089
1090            // Private location data coin flips
1091            let longitude = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) && privateData != nil ? (privateData!["longitude"] as! Fix64?) : nil
1092            if longitude != nil { revealedFields.append("longitude") }
1093            
1094            let latitude = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) && privateData != nil ? (privateData!["latitude"] as! Fix64?) : nil
1095            if latitude != nil { revealedFields.append("latitude") }
1096            
1097            let waterTemp = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) && privateData != nil ? (privateData!["waterTemp"] as! UFix64?) : nil
1098            if waterTemp != nil { revealedFields.append("waterTemp") }
1099            
1100            let airTemp = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) && privateData != nil ? (privateData!["airTemp"] as! UFix64?) : nil
1101            if airTemp != nil { revealedFields.append("airTemp") }
1102            
1103            let weather = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) && privateData != nil ? (privateData!["weather"] as! String?) : nil
1104            if weather != nil { revealedFields.append("weather") }
1105            
1106            let moonPhase = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) && privateData != nil ? (privateData!["moonPhase"] as! String?) : nil
1107            if moonPhase != nil { revealedFields.append("moonPhase") }
1108            
1109            let tide = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) && privateData != nil ? (privateData!["tide"] as! String?) : nil
1110            if tide != nil { revealedFields.append("tide") }
1111
1112            // Private angler data coin flips
1113            let location = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) && privateData != nil ? (privateData!["location"] as! String?) : nil
1114            if location != nil { revealedFields.append("location") }
1115            
1116            let gear = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) && privateData != nil ? (privateData!["gear"] as! String?) : nil
1117            if gear != nil { revealedFields.append("gear") }
1118            
1119            let baitLure = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) && privateData != nil ? (privateData!["baitLure"] as! String?) : nil
1120            if baitLure != nil { revealedFields.append("baitLure") }
1121            
1122            let technique = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) && privateData != nil ? (privateData!["technique"] as! String?) : nil
1123            if technique != nil { revealedFields.append("technique") }
1124            
1125            let girth = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) && privateData != nil ? (privateData!["girth"] as! UFix64?) : nil
1126            if girth != nil { revealedFields.append("girth") }
1127            
1128            let fightDuration = self.coinFlip(prg: &prg as &Xorshift128plus.PRG) && privateData != nil ? (privateData!["fightDuration"] as! UFix64?) : nil
1129            if fightDuration != nil { revealedFields.append("fightDuration") }
1130
1131            // Create FishCard metadata
1132            let cardMetadata = FishCardMetadata(
1133                fishNFTId: fishNFT.id,
1134                originalOwner: fishMetadata.owner,
1135                species: fishMetadata.species,
1136                scientific: fishMetadata.scientific,
1137                length: fishMetadata.length,
1138                timestamp: fishMetadata.timestamp,
1139                speciesCode: fishMetadata.speciesCode,
1140                hasRelease: fishMetadata.hasRelease,
1141                weight: weight,
1142                qualityScore: qualityScore,
1143                waterBody: waterBody,
1144                verificationLevel: verificationLevel,
1145                bumpShotUrl: bumpShotUrl,
1146                heroShotUrl: heroShotUrl,
1147                longitude: longitude,
1148                latitude: latitude,
1149                waterTemp: waterTemp,
1150                airTemp: airTemp,
1151                weather: weather,
1152                moonPhase: moonPhase,
1153                tide: tide,
1154                location: location,
1155                gear: gear,
1156                baitLure: baitLure,
1157                technique: technique,
1158                girth: girth,
1159                fightDuration: fightDuration,
1160                revealedFields: revealedFields
1161            )
1162
1163            // Create and return the FishCard
1164            let newFishCard <- create FishCard(
1165                id: self.nextCardID,
1166                metadata: cardMetadata,
1167                mintedBy: FishNFT.account.address
1168            )
1169
1170            emit FishCardMinted(
1171                id: self.nextCardID,
1172                fishNFTId: fishNFT.id,
1173                recipient: commit.recipient,
1174                species: fishMetadata.species,
1175                revealedFields: revealedFields
1176            )
1177
1178            emit FishCardRevealed(
1179                commitId: receipt.commitId,
1180                fishCardId: self.nextCardID,
1181                revealedFields: revealedFields
1182            )
1183
1184            // Update counts and cleanup
1185            FishNFT.totalFishCards = FishNFT.totalFishCards + 1
1186            self.nextCardID = self.nextCardID + 1
1187            FishNFT.activeCommits.remove(key: receipt.commitId)
1188
1189            // Clean up receipt
1190            destroy receipt
1191
1192            return <-newFishCard
1193        }
1194
1195        // Simple coin flip using PRG
1196        access(contract) fun coinFlip(prg: &Xorshift128plus.PRG): Bool {
1197            let randomValue = prg.nextUInt64()
1198            return randomValue % 2 == 0
1199        }
1200    }
1201
1202    // Public query functions
1203    access(all) view fun getTotalFishCaught(): UInt64 {
1204        return self.totalFishCaught
1205    }
1206
1207    access(all) view fun getTotalFishCards(): UInt64 {
1208        return self.totalFishCards
1209    }
1210
1211    // Public FishCard commit function - anyone can call to start the process
1212    access(all) fun commitFishCard(
1213        fishNFTId: UInt64,
1214        fishNFTOwner: Address,
1215        recipient: Address,
1216        userSalt: [UInt8]
1217    ): @FishCardReceipt {
1218        // Get minter reference
1219        let minterRef = self.account.storage.borrow<&FishCardMinter>(from: self.FishCardMinterStoragePath)
1220            ?? panic("Could not borrow FishCard minter")
1221
1222        // Commit the card request
1223        return <- minterRef.commitFishCard(
1224            fishNFTId: fishNFTId,
1225            fishNFTOwner: fishNFTOwner,
1226            recipient: recipient,
1227            userSalt: userSalt
1228        )
1229    }
1230
1231    // Public FishCard reveal function - reveals the card from a receipt
1232    access(all) fun revealFishCard(receipt: @FishCardReceipt): @FishNFT.FishCard {
1233        // Get minter reference
1234        let minterRef = self.account.storage.borrow<&FishCardMinter>(from: self.FishCardMinterStoragePath)
1235            ?? panic("Could not borrow FishCard minter")
1236
1237        // Reveal the card
1238        return <- minterRef.revealFishCard(receipt: <-receipt)
1239    }
1240
1241    // Contract Views (required by NonFungibleToken interface)
1242    access(all) view fun getContractViews(resourceType: Type?): [Type] {
1243        return [
1244            Type<MetadataViews.NFTCollectionData>(),
1245            Type<MetadataViews.NFTCollectionDisplay>()
1246        ]
1247    }
1248
1249    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
1250        switch viewType {
1251            case Type<MetadataViews.NFTCollectionData>():
1252                return MetadataViews.NFTCollectionData(
1253                    storagePath: self.CollectionStoragePath,
1254                    publicPath: self.CollectionPublicPath,
1255                    publicCollection: Type<&FishNFT.Collection>(),
1256                    publicLinkedType: Type<&FishNFT.Collection>(),
1257                    createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
1258                        return <-FishNFT.createEmptyCollection(nftType: Type<@FishNFT.NFT>())
1259                    })
1260                )
1261            case Type<MetadataViews.NFTCollectionDisplay>():
1262                return MetadataViews.NFTCollectionDisplay(
1263                    name: "DerbyFish Catches",
1264                    description: "Verified fishing catches in the DerbyFish ecosystem",
1265                    externalURL: MetadataViews.ExternalURL("https://derby.fish"),
1266                    squareImage: MetadataViews.Media(
1267                        file: MetadataViews.HTTPFile(url: "https://derby.fish/images/logo-square.png"),
1268                        mediaType: "image/png"
1269                    ),
1270                    bannerImage: MetadataViews.Media(
1271                        file: MetadataViews.HTTPFile(url: "https://derby.fish/images/banner.png"),
1272                        mediaType: "image/png"
1273                    ),
1274                    socials: {
1275                        "website": MetadataViews.ExternalURL("https://derby.fish"),
1276                        "twitter": MetadataViews.ExternalURL("https://twitter.com/derby_fish")
1277                    }
1278                )
1279        }
1280        return nil
1281    }
1282
1283    // Simple species registry functions
1284    access(all) fun registerSpecies(speciesCode: String, contractAddress: Address) {
1285        self.speciesRegistry[speciesCode] = contractAddress
1286        emit SpeciesRegistered(speciesCode: speciesCode, contractAddress: contractAddress)
1287    }
1288
1289    access(all) fun getSpeciesAddress(speciesCode: String): Address? {
1290        return self.speciesRegistry[speciesCode]
1291    }
1292
1293    access(all) view fun getAllRegisteredSpecies(): {String: Address} {
1294        return self.speciesRegistry
1295    }
1296
1297    init() {
1298        self.CollectionStoragePath = /storage/FishNFTCollection
1299        self.CollectionPublicPath = /public/FishNFTCollection
1300        self.MinterStoragePath = /storage/FishNFTMinter
1301
1302        // Initialize FishCard storage paths
1303        self.FishCardCollectionStoragePath = /storage/FishCardCollection
1304        self.FishCardCollectionPublicPath = /public/FishCardCollection
1305        self.FishCardMinterStoragePath = /storage/FishCardMinter
1306
1307        // Initialize species integration variables
1308        self.speciesRegistry = {}
1309        self.totalFishCaught = 0
1310        self.totalFishCards = 0
1311        self.nextCommitId = 0
1312        self.activeCommits = {}
1313
1314        // Set FishCard Receipt storage path
1315        self.FishCardReceiptStoragePath = StoragePath(identifier: "FishCardReceipt_".concat(self.account.address.toString()))!
1316
1317        // Create Fish NFT collection and minter
1318        let collection <- create Collection()
1319        self.account.storage.save(<-collection, to: self.CollectionStoragePath)
1320
1321        let collectionCap = self.account.capabilities.storage.issue<&FishNFT.Collection>(self.CollectionStoragePath)
1322        self.account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
1323
1324        let minter <- create NFTMinter()
1325        self.account.storage.save(<-minter, to: self.MinterStoragePath)
1326
1327        // Create FishCard collection and minter
1328        let fishCardCollection <- create FishCardCollection()
1329        self.account.storage.save(<-fishCardCollection, to: self.FishCardCollectionStoragePath)
1330
1331        let fishCardCollectionCap = self.account.capabilities.storage.issue<&FishNFT.FishCardCollection>(self.FishCardCollectionStoragePath)
1332        self.account.capabilities.publish(fishCardCollectionCap, at: self.FishCardCollectionPublicPath)
1333
1334        let fishCardMinter <- create FishCardMinter()
1335        self.account.storage.save(<-fishCardMinter, to: self.FishCardMinterStoragePath)
1336    }
1337}