Smart Contract
SnookCoin
A.44100f14f70e3f78.SnookCoin
1import FungibleToken from 0xf233dcee88fe0abe
2import MetadataViews from 0x1d7e57aa55817448
3import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
4
5access(all) contract SnookCoin: FungibleToken {
6
7 // FISHDEX INTEGRATION - Cross-contract coordination interface
8 access(all) resource interface SpeciesCoinPublic {
9 access(all) view fun getSpeciesCode(): String
10 access(all) view fun getTicker(): String
11 access(all) view fun getCommonName(): String
12 access(all) view fun getScientificName(): String
13 access(all) view fun getFamily(): String
14 access(all) view fun getTotalSupply(): UFix64
15 access(all) view fun getBasicRegistryInfo(): {String: AnyStruct}
16 access(all) fun redeemCatchNFT(fishData: {String: AnyStruct}, angler: Address): @SnookCoin.Vault
17 }
18
19 // FISHDEX COORDINATION - Registry management
20 access(all) var fishDEXAddress: Address?
21 access(all) var isRegisteredWithFishDEX: Bool
22
23 // Regional data structures for location-specific information
24 access(all) struct RegionalRegulations {
25 access(all) var sizeLimit: UFix64? // Minimum legal size in inches
26 access(all) var bagLimit: UInt8? // Daily catch limit
27 access(all) var closedSeasons: [String] // Protected breeding periods
28 access(all) var specialRegulations: String // Additional rules/restrictions
29 access(all) var licenseRequired: Bool // Fishing license requirement
30
31 init(sizeLimit: UFix64?, bagLimit: UInt8?, closedSeasons: [String], specialRegulations: String, licenseRequired: Bool) {
32 self.sizeLimit = sizeLimit
33 self.bagLimit = bagLimit
34 self.closedSeasons = closedSeasons
35 self.specialRegulations = specialRegulations
36 self.licenseRequired = licenseRequired
37 }
38 }
39
40 access(all) struct RegionalPopulation {
41 access(all) var populationTrend: String? // "Increasing", "Stable", "Declining", "Critical"
42 access(all) var threats: [String] // Region-specific threats
43 access(all) var protectedAreas: [String] // Local protected areas
44 access(all) var estimatedPopulation: UInt64? // If known
45
46 init(populationTrend: String?, threats: [String], protectedAreas: [String], estimatedPopulation: UInt64?) {
47 self.populationTrend = populationTrend
48 self.threats = threats
49 self.protectedAreas = protectedAreas
50 self.estimatedPopulation = estimatedPopulation
51 }
52 }
53
54 // Events
55 access(all) event TokensMinted(amount: UFix64, to: Address?)
56 access(all) event TokensBurned(amount: UFix64, from: Address?)
57 access(all) event CatchVerified(fishId: UInt64, angler: Address, amount: UFix64)
58 access(all) event MetadataUpdated(field: String, oldValue: String, newValue: String)
59 access(all) event FirstCatchRecorded(timestamp: UInt64, angler: Address)
60 access(all) event YearlyMetadataCreated(year: UInt64)
61 access(all) event MetadataYearUpdated(oldYear: UInt64, newYear: UInt64)
62 access(all) event NFTAlreadyMinted(fishId: UInt64, angler: Address)
63
64 // FISHDEX INTEGRATION EVENTS
65 access(all) event FishDEXRegistrationAttempted(fishDEXAddress: Address, speciesCode: String)
66 access(all) event FishDEXRegistrationCompleted(fishDEXAddress: Address, speciesCode: String)
67 access(all) event FishDEXAddressUpdated(oldAddress: Address?, newAddress: Address)
68 access(all) event CatchProcessedFromNFT(fishNFTId: UInt64?, angler: Address, amount: UFix64)
69
70 // Total supply
71 access(all) var totalSupply: UFix64
72
73 // Track which NFTs have been used for minting
74 access(all) var fishNFTRegistry: {UInt64: Bool}
75
76 // Temporal metadata system - Track yearly updates
77 access(all) var currentMetadataYear: UInt64
78 access(all) var metadataHistory: {UInt64: SpeciesMetadata}
79 access(all) var speciesMetadata: SpeciesMetadata // Current year metadata (pointer to latest)
80
81 // Regional context for location-specific operations
82 access(all) var defaultRegion: String
83
84 // Storage paths
85 access(all) let VaultStoragePath: StoragePath
86 access(all) let VaultPublicPath: PublicPath
87 access(all) let MinterStoragePath: StoragePath
88 access(all) let MetadataAdminStoragePath: StoragePath
89 access(all) let FishDEXCoordinatorStoragePath: StoragePath
90 access(all) let FishDEXCoordinatorPublicPath: PublicPath
91
92 // Species metadata - HYBRID: Core fields immutable, descriptive fields mutable + REGIONAL + TEMPORAL
93 access(all) struct SpeciesMetadata {
94 // IMMUTABLE - Core identity fields that should never change
95 access(all) let commonName: String // e.g., "Example Fish"
96 access(all) let speciesCode: String // e.g., "EXAMPLE_FISH"
97 access(all) let ticker: String // e.g., "EXFISH"
98 access(all) let scientificName: String // e.g., "Example fish"
99 access(all) let family: String // e.g., "Example family"
100 access(all) let dataYear: UInt64 // Year this metadata represents
101
102 // MUTABLE - Descriptive fields that can be updated (NULLABLE WHERE APPROPRIATE)
103 access(all) var habitat: String? // e.g., "Freshwater" - nullable if unknown
104 access(all) var averageWeight: UFix64? // in pounds - nullable if unknown
105 access(all) var averageLength: UFix64? // in inches - nullable if unknown
106 access(all) var imageURL: String? // species reference image - nullable
107 access(all) var description: String // species description
108 access(all) var firstCatchDate: UInt64? // timestamp of first verified catch
109 access(all) var rarityTier: UInt8? // 1=Common, 2=Uncommon, 3=Rare, 4=Epic, 5=Legendary
110
111 // GLOBAL CONSERVATION & POPULATION INTELLIGENCE
112 access(all) var globalConservationStatus: String? // IUCN status: "Least Concern", "Threatened", etc.
113 access(all) var regionalPopulations: {String: RegionalPopulation} // Region-specific population data
114
115 // BIOLOGICAL INTELLIGENCE (NULLABLE WHERE DATA MAY BE MISSING)
116 access(all) var lifespan: UFix64? // Maximum age in years
117 access(all) var diet: String? // Primary food sources
118 access(all) var predators: [String] // Natural predators (can be empty)
119 access(all) var temperatureRange: String? // Preferred water temps
120 access(all) var depthRange: String? // Habitat depth range
121 access(all) var spawningAge: UFix64? // Sexual maturity age in years
122 access(all) var spawningBehavior: String? // Detailed spawning patterns
123 access(all) var migrationPattern: String? // Migration behavior
124 access(all) var waterQualityNeeds: String? // pH, oxygen, salinity requirements
125
126 // GEOGRAPHIC & HABITAT INTELLIGENCE
127 access(all) var nativeRegions: [String] // ["North America", "Great Lakes", "Mississippi River"]
128 access(all) var currentRange: [String] // Current distribution (may differ from native)
129 access(all) var waterTypes: [String] // ["River", "Lake", "Stream", "Reservoir"]
130 access(all) var invasiveStatus: String? // "Native", "Introduced", "Invasive", "Hybrid"
131
132 // ECONOMIC & COMMERCIAL INTELLIGENCE (REGIONAL)
133 access(all) var regionalCommercialValue: {String: UFix64} // Market price per pound by region
134 access(all) var tourismValue: UInt8? // Tourism draw rating 1-10
135 access(all) var ecosystemRole: String? // "Apex Predator", "Baitfish", "Bottom Feeder"
136 access(all) var culturalSignificance: String? // Historical/cultural importance
137
138 // ANGLING & RECREATIONAL INTELLIGENCE
139 access(all) var bestBaits: [String] // Most effective baits
140 access(all) var fightRating: UInt8? // Fight intensity 1-10
141 access(all) var culinaryRating: UInt8? // Eating quality 1-10
142 access(all) var catchDifficulty: UInt8? // How hard to catch 1-10
143 access(all) var seasonalAvailability: String? // When most catchable
144 access(all) var bestTechniques: [String] // Preferred fishing methods
145
146 // REGIONAL REGULATORY INTELLIGENCE
147 access(all) var regionalRegulations: {String: RegionalRegulations} // Region-specific rules
148
149 // PHYSICAL & BEHAVIORAL CHARACTERISTICS
150 access(all) var physicalDescription: String? // Colors, patterns, distinguishing features
151 access(all) var behaviorTraits: String? // Feeding habits, aggression, schooling
152 access(all) var seasonalPatterns: String? // Activity throughout the year
153
154 // RECORDS & ACHIEVEMENTS
155 access(all) var recordWeight: UFix64? // World record weight in pounds
156 access(all) var recordWeightLocation: String? // Where weight record was caught
157 access(all) var recordWeightDate: String? // When weight record was set
158 access(all) var recordLength: UFix64? // World record length in inches
159 access(all) var recordLengthLocation: String? // Where length record was caught
160 access(all) var recordLengthDate: String? // When length record was set
161
162 // RESEARCH & SCIENTIFIC INTELLIGENCE
163 access(all) var researchPriority: UInt8? // Scientific research importance 1-10
164 access(all) var geneticMarkers: String? // DNA/genetic information
165 access(all) var studyPrograms: [String] // Active research programs
166
167 // FLEXIBLE METADATA SYSTEM
168 access(all) var additionalMetadata: {String: String} // Custom key-value pairs for future expansion
169
170 // BASIC FIELD SETTERS
171 access(all) fun setHabitat(_ newHabitat: String) { self.habitat = newHabitat }
172 access(all) fun setAverageWeight(_ newWeight: UFix64) { self.averageWeight = newWeight }
173 access(all) fun setAverageLength(_ newLength: UFix64) { self.averageLength = newLength }
174 access(all) fun setImageURL(_ newURL: String) { self.imageURL = newURL }
175 access(all) fun setDescription(_ newDescription: String) { self.description = newDescription }
176 access(all) fun setFirstCatchDate(_ newDate: UInt64?) { self.firstCatchDate = newDate }
177 access(all) fun setRarityTier(_ newTier: UInt8) { self.rarityTier = newTier }
178
179 // CONSERVATION & POPULATION SETTERS
180 access(all) fun setConservationStatus(_ newStatus: String) { self.globalConservationStatus = newStatus }
181 access(all) fun setRegionalPopulationTrend(_ region: String, _ newTrend: String?) {
182 if let existing = self.regionalPopulations[region] {
183 self.regionalPopulations[region] = RegionalPopulation(
184 populationTrend: newTrend,
185 threats: existing.threats,
186 protectedAreas: existing.protectedAreas,
187 estimatedPopulation: existing.estimatedPopulation
188 )
189 }
190 }
191 access(all) fun setRegionalThreats(_ region: String, _ newThreats: [String]) {
192 if let existing = self.regionalPopulations[region] {
193 self.regionalPopulations[region] = RegionalPopulation(
194 populationTrend: existing.populationTrend,
195 threats: newThreats,
196 protectedAreas: existing.protectedAreas,
197 estimatedPopulation: existing.estimatedPopulation
198 )
199 }
200 }
201 access(all) fun setRegionalProtectedAreas(_ region: String, _ newAreas: [String]) {
202 if let existing = self.regionalPopulations[region] {
203 self.regionalPopulations[region] = RegionalPopulation(
204 populationTrend: existing.populationTrend,
205 threats: existing.threats,
206 protectedAreas: newAreas,
207 estimatedPopulation: existing.estimatedPopulation
208 )
209 }
210 }
211
212 // BIOLOGICAL SETTERS
213 access(all) fun setLifespan(_ newLifespan: UFix64) { self.lifespan = newLifespan }
214 access(all) fun setDiet(_ newDiet: String) { self.diet = newDiet }
215 access(all) fun setPredators(_ newPredators: [String]) { self.predators = newPredators }
216 access(all) fun setTemperatureRange(_ newRange: String) { self.temperatureRange = newRange }
217 access(all) fun setDepthRange(_ newRange: String) { self.depthRange = newRange }
218 access(all) fun setSpawningAge(_ newAge: UFix64) { self.spawningAge = newAge }
219 access(all) fun setSpawningBehavior(_ newBehavior: String) { self.spawningBehavior = newBehavior }
220 access(all) fun setMigrationPattern(_ newPattern: String) { self.migrationPattern = newPattern }
221 access(all) fun setWaterQualityNeeds(_ newNeeds: String) { self.waterQualityNeeds = newNeeds }
222
223 // GEOGRAPHIC & HABITAT SETTERS
224 access(all) fun setNativeRegions(_ newRegions: [String]) { self.nativeRegions = newRegions }
225 access(all) fun setCurrentRange(_ newRange: [String]) { self.currentRange = newRange }
226 access(all) fun setWaterTypes(_ newTypes: [String]) { self.waterTypes = newTypes }
227 access(all) fun setInvasiveStatus(_ newStatus: String) { self.invasiveStatus = newStatus }
228
229 // ECONOMIC & COMMERCIAL SETTERS
230 access(all) fun setRegionalCommercialValue(_ region: String, _ newValue: UFix64) { self.regionalCommercialValue[region] = newValue }
231 access(all) fun setTourismValue(_ newValue: UInt8?) { self.tourismValue = newValue }
232 access(all) fun setEcosystemRole(_ newRole: String?) { self.ecosystemRole = newRole }
233 access(all) fun setCulturalSignificance(_ newSignificance: String?) { self.culturalSignificance = newSignificance }
234
235 // ANGLING & RECREATIONAL SETTERS
236 access(all) fun setBestBaits(_ newBaits: [String]) { self.bestBaits = newBaits }
237 access(all) fun setFightRating(_ newRating: UInt8?) { self.fightRating = newRating }
238 access(all) fun setCulinaryRating(_ newRating: UInt8?) { self.culinaryRating = newRating }
239 access(all) fun setCatchDifficulty(_ newDifficulty: UInt8?) { self.catchDifficulty = newDifficulty }
240 access(all) fun setSeasonalAvailability(_ newAvailability: String?) { self.seasonalAvailability = newAvailability }
241 access(all) fun setBestTechniques(_ newTechniques: [String]) { self.bestTechniques = newTechniques }
242
243 // REGULATORY SETTERS
244 access(all) fun setRegionalSizeLimit(_ region: String, _ newLimit: UFix64?) {
245 if let existing = self.regionalRegulations[region] {
246 self.regionalRegulations[region] = RegionalRegulations(
247 sizeLimit: newLimit,
248 bagLimit: existing.bagLimit,
249 closedSeasons: existing.closedSeasons,
250 specialRegulations: existing.specialRegulations,
251 licenseRequired: existing.licenseRequired
252 )
253 }
254 }
255 access(all) fun setRegionalBagLimit(_ region: String, _ newLimit: UInt8?) {
256 if let existing = self.regionalRegulations[region] {
257 self.regionalRegulations[region] = RegionalRegulations(
258 sizeLimit: existing.sizeLimit,
259 bagLimit: newLimit,
260 closedSeasons: existing.closedSeasons,
261 specialRegulations: existing.specialRegulations,
262 licenseRequired: existing.licenseRequired
263 )
264 }
265 }
266 access(all) fun setRegionalClosedSeasons(_ region: String, _ newSeasons: [String]) {
267 if let existing = self.regionalRegulations[region] {
268 self.regionalRegulations[region] = RegionalRegulations(
269 sizeLimit: existing.sizeLimit,
270 bagLimit: existing.bagLimit,
271 closedSeasons: newSeasons,
272 specialRegulations: existing.specialRegulations,
273 licenseRequired: existing.licenseRequired
274 )
275 }
276 }
277 access(all) fun setRegionalSpecialRegulations(_ region: String, _ newRegulations: String) {
278 if let existing = self.regionalRegulations[region] {
279 self.regionalRegulations[region] = RegionalRegulations(
280 sizeLimit: existing.sizeLimit,
281 bagLimit: existing.bagLimit,
282 closedSeasons: existing.closedSeasons,
283 specialRegulations: newRegulations,
284 licenseRequired: existing.licenseRequired
285 )
286 }
287 }
288
289 // PHYSICAL & BEHAVIORAL SETTERS
290 access(all) fun setPhysicalDescription(_ newDescription: String) { self.physicalDescription = newDescription }
291 access(all) fun setBehaviorTraits(_ newTraits: String) { self.behaviorTraits = newTraits }
292 access(all) fun setSeasonalPatterns(_ newPatterns: String) { self.seasonalPatterns = newPatterns }
293
294 // RECORDS & ACHIEVEMENTS SETTERS
295 access(all) fun setRecordWeight(_ newRecord: UFix64?) { self.recordWeight = newRecord }
296 access(all) fun setRecordWeightLocation(_ newLocation: String?) { self.recordWeightLocation = newLocation }
297 access(all) fun setRecordWeightDate(_ newDate: String?) { self.recordWeightDate = newDate }
298 access(all) fun setRecordLength(_ newRecord: UFix64?) { self.recordLength = newRecord }
299 access(all) fun setRecordLengthLocation(_ newLocation: String?) { self.recordLengthLocation = newLocation }
300 access(all) fun setRecordLengthDate(_ newDate: String?) { self.recordLengthDate = newDate }
301
302 // RESEARCH & SCIENTIFIC SETTERS
303 access(all) fun setResearchPriority(_ newPriority: UInt8) { self.researchPriority = newPriority }
304 access(all) fun setGeneticMarkers(_ newMarkers: String) { self.geneticMarkers = newMarkers }
305 access(all) fun setStudyPrograms(_ newPrograms: [String]) { self.studyPrograms = newPrograms }
306
307 // FLEXIBLE METADATA SETTERS
308 access(all) fun setAdditionalMetadata(_ newMetadata: {String: String}) { self.additionalMetadata = newMetadata }
309 access(all) fun updateMetadataField(_ key: String, _ value: String) { self.additionalMetadata[key] = value }
310
311 init(
312 // IMMUTABLE CORE FIELDS
313 speciesCode: String,
314 ticker: String,
315 scientificName: String,
316 family: String,
317 dataYear: UInt64,
318
319 // BASIC DESCRIPTIVE FIELDS
320 commonName: String,
321 habitat: String?,
322 averageWeight: UFix64?,
323 averageLength: UFix64?,
324 imageURL: String?,
325 description: String,
326 firstCatchDate: UInt64?,
327 rarityTier: UInt8?,
328
329 // CONSERVATION & POPULATION
330 globalConservationStatus: String?,
331 regionalPopulations: {String: RegionalPopulation},
332
333 // BIOLOGICAL INTELLIGENCE
334 lifespan: UFix64?,
335 diet: String?,
336 predators: [String],
337 temperatureRange: String?,
338 depthRange: String?,
339 spawningAge: UFix64?,
340 spawningBehavior: String?,
341 migrationPattern: String?,
342 waterQualityNeeds: String?,
343
344 // GEOGRAPHIC & HABITAT
345 nativeRegions: [String],
346 currentRange: [String],
347 waterTypes: [String],
348 invasiveStatus: String?,
349
350 // ECONOMIC & COMMERCIAL
351 regionalCommercialValue: {String: UFix64},
352 tourismValue: UInt8?,
353 ecosystemRole: String?,
354 culturalSignificance: String?,
355
356 // ANGLING & RECREATIONAL
357 bestBaits: [String],
358 fightRating: UInt8?,
359 culinaryRating: UInt8?,
360 catchDifficulty: UInt8?,
361 seasonalAvailability: String?,
362 bestTechniques: [String],
363
364 // REGULATORY
365 regionalRegulations: {String: RegionalRegulations},
366
367 // PHYSICAL & BEHAVIORAL
368 physicalDescription: String?,
369 behaviorTraits: String?,
370 seasonalPatterns: String?,
371
372 // RECORDS & ACHIEVEMENTS
373 recordWeight: UFix64?,
374 recordWeightLocation: String?,
375 recordWeightDate: String?,
376 recordLength: UFix64?,
377 recordLengthLocation: String?,
378 recordLengthDate: String?,
379
380 // RESEARCH & SCIENTIFIC
381 researchPriority: UInt8?,
382 geneticMarkers: String?,
383 studyPrograms: [String],
384
385 // FLEXIBLE METADATA
386 additionalMetadata: {String: String}
387 ) {
388 // Set immutable fields
389 self.speciesCode = speciesCode
390 self.ticker = ticker
391 self.scientificName = scientificName
392 self.family = family
393 self.dataYear = dataYear
394
395 // Set basic descriptive fields
396 self.commonName = commonName
397 self.habitat = habitat
398 self.averageWeight = averageWeight
399 self.averageLength = averageLength
400 self.imageURL = imageURL
401 self.description = description
402 self.firstCatchDate = firstCatchDate
403 self.rarityTier = rarityTier
404
405 // Set conservation & population fields
406 self.globalConservationStatus = globalConservationStatus
407 self.regionalPopulations = regionalPopulations
408
409 // Set biological intelligence fields
410 self.lifespan = lifespan
411 self.diet = diet
412 self.predators = predators
413 self.temperatureRange = temperatureRange
414 self.depthRange = depthRange
415 self.spawningAge = spawningAge
416 self.spawningBehavior = spawningBehavior
417 self.migrationPattern = migrationPattern
418 self.waterQualityNeeds = waterQualityNeeds
419
420 // Set geographic & habitat fields
421 self.nativeRegions = nativeRegions
422 self.currentRange = currentRange
423 self.waterTypes = waterTypes
424 self.invasiveStatus = invasiveStatus
425
426 // Set economic & commercial fields
427 self.regionalCommercialValue = regionalCommercialValue
428 self.tourismValue = tourismValue
429 self.ecosystemRole = ecosystemRole
430 self.culturalSignificance = culturalSignificance
431
432 // Set angling & recreational fields
433 self.bestBaits = bestBaits
434 self.fightRating = fightRating
435 self.culinaryRating = culinaryRating
436 self.catchDifficulty = catchDifficulty
437 self.seasonalAvailability = seasonalAvailability
438 self.bestTechniques = bestTechniques
439
440 // Set regulatory fields
441 self.regionalRegulations = regionalRegulations
442
443 // Set physical & behavioral fields
444 self.physicalDescription = physicalDescription
445 self.behaviorTraits = behaviorTraits
446 self.seasonalPatterns = seasonalPatterns
447
448 // Set records & achievements fields
449 self.recordWeight = recordWeight
450 self.recordWeightLocation = recordWeightLocation
451 self.recordWeightDate = recordWeightDate
452 self.recordLength = recordLength
453 self.recordLengthLocation = recordLengthLocation
454 self.recordLengthDate = recordLengthDate
455
456 // Set research & scientific fields
457 self.researchPriority = researchPriority
458 self.geneticMarkers = geneticMarkers
459 self.studyPrograms = studyPrograms
460
461 // Set flexible metadata
462 self.additionalMetadata = additionalMetadata
463 }
464 }
465
466 // Contract Views
467 access(all) view fun getContractViews(resourceType: Type?): [Type] {
468 return [
469 Type<FungibleTokenMetadataViews.FTView>(),
470 Type<FungibleTokenMetadataViews.FTDisplay>(),
471 Type<FungibleTokenMetadataViews.FTVaultData>(),
472 Type<FungibleTokenMetadataViews.TotalSupply>()
473 ]
474 }
475
476 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
477 switch viewType {
478 case Type<FungibleTokenMetadataViews.FTDisplay>():
479 let media = MetadataViews.Media(
480 file: MetadataViews.HTTPFile(url: self.speciesMetadata.imageURL ?? ""),
481 mediaType: "image/jpeg"
482 )
483 return FungibleTokenMetadataViews.FTDisplay(
484 name: self.speciesMetadata.commonName.concat(" Coin"),
485 symbol: self.speciesMetadata.ticker,
486 description: self.speciesMetadata.description,
487 externalURL: MetadataViews.ExternalURL("https://derby.fish/species/".concat(self.speciesMetadata.speciesCode.toLower())),
488 logos: MetadataViews.Medias([media]),
489 socials: {
490 "website": MetadataViews.ExternalURL("https://derby.fish"),
491 "twitter": MetadataViews.ExternalURL("https://twitter.com/derbyfish")
492 }
493 )
494 case Type<FungibleTokenMetadataViews.FTVaultData>():
495 return FungibleTokenMetadataViews.FTVaultData(
496 storagePath: self.VaultStoragePath,
497 receiverPath: self.VaultPublicPath,
498 metadataPath: self.VaultPublicPath,
499 receiverLinkedType: Type<&SnookCoin.Vault>(),
500 metadataLinkedType: Type<&SnookCoin.Vault>(),
501 createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
502 return <-SnookCoin.createEmptyVault(vaultType: Type<@SnookCoin.Vault>())
503 })
504 )
505 case Type<FungibleTokenMetadataViews.TotalSupply>():
506 return FungibleTokenMetadataViews.TotalSupply(totalSupply: self.totalSupply)
507 }
508 return nil
509 }
510
511 // FISHDEX COORDINATOR RESOURCE - Handles cross-contract integration
512 access(all) resource FishDEXCoordinator: SpeciesCoinPublic {
513
514 // Interface implementation for FishDEX registry
515 access(all) view fun getSpeciesCode(): String {
516 return SnookCoin.speciesMetadata.speciesCode
517 }
518
519 access(all) view fun getTicker(): String {
520 return SnookCoin.speciesMetadata.ticker
521 }
522
523 access(all) view fun getCommonName(): String {
524 return SnookCoin.speciesMetadata.commonName
525 }
526
527 access(all) view fun getScientificName(): String {
528 return SnookCoin.speciesMetadata.scientificName
529 }
530
531 access(all) view fun getFamily(): String {
532 return SnookCoin.speciesMetadata.family
533 }
534
535 access(all) view fun getTotalSupply(): UFix64 {
536 return SnookCoin.totalSupply
537 }
538
539 access(all) view fun getBasicRegistryInfo(): {String: AnyStruct} {
540 return {
541 "speciesCode": self.getSpeciesCode(),
542 "ticker": self.getTicker(),
543 "commonName": self.getCommonName(),
544 "scientificName": self.getScientificName(),
545 "family": self.getFamily(),
546 "totalSupply": self.getTotalSupply(),
547 "contractAddress": SnookCoin.account.address,
548 "dataYear": SnookCoin.speciesMetadata.dataYear,
549 "conservationStatus": SnookCoin.speciesMetadata.globalConservationStatus,
550 "rarityTier": SnookCoin.speciesMetadata.rarityTier,
551 "isRegistered": SnookCoin.isRegisteredWithFishDEX
552 }
553 }
554
555 // Process catch verification from Fish NFT contracts
556 access(all) fun redeemCatchNFT(fishData: {String: AnyStruct}, angler: Address): @SnookCoin.Vault {
557 // Validate fish data matches this species
558 if let fishSpeciesCode = fishData["speciesCode"] as? String {
559 assert(
560 fishSpeciesCode == self.getSpeciesCode(),
561 message: "Fish species code does not match this species coin"
562 )
563 }
564
565 // Extract fish NFT ID if available
566 let fishNFTId = fishData["nftId"] as? UInt64
567
568 // Validate NFT hasn't been redeemed before
569 if let nftId = fishNFTId {
570 assert(
571 SnookCoin.fishNFTRegistry[nftId] == nil,
572 message: "This NFT has already been redeemed for a coin"
573 )
574 // Record this NFT as redeemed
575 SnookCoin.fishNFTRegistry[nftId] = true
576 }
577
578 // Auto-record first catch if this is the very first mint
579 if SnookCoin.totalSupply == 0.0 && SnookCoin.speciesMetadata.firstCatchDate == nil {
580 SnookCoin.speciesMetadata.setFirstCatchDate(UInt64(getCurrentBlock().timestamp))
581 emit FirstCatchRecorded(timestamp: UInt64(getCurrentBlock().timestamp), angler: angler)
582 }
583
584 // Mint 1 coin for verified catch
585 let amount: UFix64 = 1.0
586 SnookCoin.totalSupply = SnookCoin.totalSupply + amount
587
588 // Create vault with the minted amount (not empty!)
589 let vault <- create Vault(balance: amount)
590
591 // Emit events
592 emit TokensMinted(amount: amount, to: angler)
593 emit CatchProcessedFromNFT(fishNFTId: fishNFTId, angler: angler, amount: amount)
594
595 if let nftId = fishNFTId {
596 emit CatchVerified(fishId: nftId, angler: angler, amount: amount)
597 }
598
599 return <- vault
600 }
601
602 // Register this species with FishDEX
603 access(all) fun registerWithFishDEX(fishDEXAddress: Address) {
604 pre {
605 !SnookCoin.isRegisteredWithFishDEX: "Already registered with FishDEX"
606 }
607
608 emit FishDEXRegistrationAttempted(fishDEXAddress: fishDEXAddress, speciesCode: self.getSpeciesCode())
609
610 // Get reference to FishDEX contract
611 let fishDEXAccount = getAccount(fishDEXAddress)
612
613 // Call FishDEX registration function
614 // Note: This will be completed when FishDEX contract is implemented
615 // For now, just update our state
616 SnookCoin.fishDEXAddress = fishDEXAddress
617 SnookCoin.isRegisteredWithFishDEX = true
618
619 emit FishDEXRegistrationCompleted(fishDEXAddress: fishDEXAddress, speciesCode: self.getSpeciesCode())
620 }
621
622 // Update FishDEX address (admin only via proper access)
623 access(all) fun updateFishDEXAddress(newAddress: Address) {
624 let oldAddress = SnookCoin.fishDEXAddress
625 SnookCoin.fishDEXAddress = newAddress
626 SnookCoin.isRegisteredWithFishDEX = false // Reset registration status
627 emit FishDEXAddressUpdated(oldAddress: oldAddress, newAddress: newAddress)
628 }
629 }
630
631 // Vault Resource
632 access(all) resource Vault: FungibleToken.Vault {
633 access(all) var balance: UFix64
634
635 init(balance: UFix64) {
636 self.balance = balance
637 }
638
639 access(contract) fun burnCallback() {
640 if self.balance > 0.0 {
641 SnookCoin.totalSupply = SnookCoin.totalSupply - self.balance
642 emit TokensBurned(amount: self.balance, from: self.owner?.address)
643 }
644 self.balance = 0.0
645 }
646
647 access(all) view fun getViews(): [Type] {
648 return SnookCoin.getContractViews(resourceType: nil)
649 }
650
651 access(all) fun resolveView(_ view: Type): AnyStruct? {
652 return SnookCoin.resolveContractView(resourceType: nil, viewType: view)
653 }
654
655 access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
656 return {Type<@SnookCoin.Vault>(): true}
657 }
658
659 access(all) view fun isSupportedVaultType(type: Type): Bool {
660 return type == Type<@SnookCoin.Vault>()
661 }
662
663 access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
664 return amount <= self.balance
665 }
666
667 access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @SnookCoin.Vault {
668 self.balance = self.balance - amount
669 return <-create Vault(balance: amount)
670 }
671
672 access(all) fun deposit(from: @{FungibleToken.Vault}) {
673 let vault <- from as! @SnookCoin.Vault
674 self.balance = self.balance + vault.balance
675 vault.balance = 0.0
676 destroy vault
677 }
678
679 access(all) fun createEmptyVault(): @SnookCoin.Vault {
680 return <-create Vault(balance: 0.0)
681 }
682 }
683
684 // Minter Resource - Admin only
685 access(all) resource Minter {
686
687 access(all) fun mintForCatch(amount: UFix64, fishId: UInt64, angler: Address): @SnookCoin.Vault {
688 pre {
689 amount == 1.0: "Only 1 coin per verified catch"
690 SnookCoin.fishNFTRegistry[fishId] == nil: "This NFT has already been used to mint a coin"
691 }
692
693 // Record this NFT as used
694 SnookCoin.fishNFTRegistry[fishId] = true
695
696 // Auto-record first catch if this is the very first mint
697 if SnookCoin.totalSupply == 0.0 && SnookCoin.speciesMetadata.firstCatchDate == nil {
698 SnookCoin.speciesMetadata.setFirstCatchDate(UInt64(getCurrentBlock().timestamp))
699 emit FirstCatchRecorded(timestamp: UInt64(getCurrentBlock().timestamp), angler: angler)
700
701 // Auto-register with FishDEX after first mint if FishDEX address is set
702 self.autoRegisterWithFishDEX()
703 }
704
705 SnookCoin.totalSupply = SnookCoin.totalSupply + amount
706
707 emit TokensMinted(amount: amount, to: angler)
708 emit CatchVerified(fishId: fishId, angler: angler, amount: amount)
709
710 return <-create Vault(balance: amount)
711 }
712
713 // Auto-registration helper function
714 access(all) fun autoRegisterWithFishDEX() {
715 if let fishDEXAddr = SnookCoin.fishDEXAddress {
716 if !SnookCoin.isRegisteredWithFishDEX {
717 // Get reference to FishDEX coordinator
718 if let coordinatorRef = SnookCoin.account.capabilities.borrow<&FishDEXCoordinator>(
719 SnookCoin.FishDEXCoordinatorPublicPath
720 ) {
721 coordinatorRef.registerWithFishDEX(fishDEXAddress: fishDEXAddr)
722 }
723 }
724 }
725 }
726
727 // Manual FishDEX registration (admin function)
728 access(all) fun registerWithFishDEXManually(fishDEXAddress: Address) {
729 SnookCoin.fishDEXAddress = fishDEXAddress
730 self.autoRegisterWithFishDEX()
731 }
732
733 access(all) fun mintBatch(recipients: {Address: UFix64}): @{Address: SnookCoin.Vault} {
734 let vaults: @{Address: SnookCoin.Vault} <- {}
735
736 for recipient in recipients.keys {
737 let amount = recipients[recipient]!
738 SnookCoin.totalSupply = SnookCoin.totalSupply + amount
739
740 let vault <- create Vault(balance: amount)
741 let oldVault <- vaults[recipient] <- vault
742 destroy oldVault
743
744 emit TokensMinted(amount: amount, to: recipient)
745 }
746
747 return <-vaults
748 }
749 }
750
751 // MetadataAdmin Resource - Admin only for updating mutable metadata fields
752 access(all) resource MetadataAdmin {
753
754 access(all) fun updateImageURL(newURL: String) {
755 let oldURL = SnookCoin.speciesMetadata.imageURL ?? ""
756 SnookCoin.speciesMetadata.setImageURL(newURL)
757 emit MetadataUpdated(field: "imageURL", oldValue: oldURL, newValue: newURL)
758 }
759
760 access(all) fun updateDescription(newDescription: String) {
761 let oldDescription = SnookCoin.speciesMetadata.description
762 SnookCoin.speciesMetadata.setDescription(newDescription)
763 emit MetadataUpdated(field: "description", oldValue: oldDescription, newValue: newDescription)
764 }
765
766
767
768 access(all) fun updateHabitat(newHabitat: String) {
769 let oldHabitat = SnookCoin.speciesMetadata.habitat ?? ""
770 SnookCoin.speciesMetadata.setHabitat(newHabitat)
771 emit MetadataUpdated(field: "habitat", oldValue: oldHabitat, newValue: newHabitat)
772 }
773
774 access(all) fun updateAverageWeight(newWeight: UFix64) {
775 let oldWeight = SnookCoin.speciesMetadata.averageWeight?.toString() ?? "0.0"
776 SnookCoin.speciesMetadata.setAverageWeight(newWeight)
777 emit MetadataUpdated(field: "averageWeight", oldValue: oldWeight, newValue: newWeight.toString())
778 }
779
780 access(all) fun updateAverageLength(newLength: UFix64) {
781 let oldLength = SnookCoin.speciesMetadata.averageLength?.toString() ?? "0.0"
782 SnookCoin.speciesMetadata.setAverageLength(newLength)
783 emit MetadataUpdated(field: "averageLength", oldValue: oldLength, newValue: newLength.toString())
784 }
785
786 access(all) fun updateRarityTier(newTier: UInt8) {
787 pre {
788 newTier >= 1 && newTier <= 5: "Invalid rarity tier (must be 1-5)"
789 }
790 let oldTier = SnookCoin.speciesMetadata.rarityTier?.toString() ?? "0"
791 SnookCoin.speciesMetadata.setRarityTier(newTier)
792 emit MetadataUpdated(field: "rarityTier", oldValue: oldTier, newValue: newTier.toString())
793 }
794
795 access(all) fun manuallySetFirstCatch(timestamp: UInt64, angler: Address) {
796 pre {
797 SnookCoin.speciesMetadata.firstCatchDate == nil: "First catch already recorded"
798 }
799 SnookCoin.speciesMetadata.setFirstCatchDate(timestamp)
800 emit FirstCatchRecorded(timestamp: timestamp, angler: angler)
801 emit MetadataUpdated(field: "firstCatchDate", oldValue: "", newValue: timestamp.toString())
802 }
803
804 // FishDEX-specific metadata updates
805 access(all) fun updateConservationStatus(newStatus: String) {
806 let oldStatus = SnookCoin.speciesMetadata.globalConservationStatus ?? ""
807 SnookCoin.speciesMetadata.setConservationStatus(newStatus)
808 emit MetadataUpdated(field: "globalConservationStatus", oldValue: oldStatus, newValue: newStatus)
809 }
810
811 access(all) fun updateNativeRegions(newRegions: [String]) {
812 var oldRegionsStr = "["
813 for i, region in SnookCoin.speciesMetadata.nativeRegions {
814 if i > 0 { oldRegionsStr = oldRegionsStr.concat(",") }
815 oldRegionsStr = oldRegionsStr.concat(region)
816 }
817 oldRegionsStr = oldRegionsStr.concat("]")
818
819 SnookCoin.speciesMetadata.setNativeRegions(newRegions)
820
821 var newRegionsStr = "["
822 for i, region in newRegions {
823 if i > 0 { newRegionsStr = newRegionsStr.concat(",") }
824 newRegionsStr = newRegionsStr.concat(region)
825 }
826 newRegionsStr = newRegionsStr.concat("]")
827
828 emit MetadataUpdated(field: "nativeRegions", oldValue: oldRegionsStr, newValue: newRegionsStr)
829 }
830
831 access(all) fun updateSeasonalPatterns(newPatterns: String) {
832 let oldPatterns = SnookCoin.speciesMetadata.seasonalPatterns ?? ""
833 SnookCoin.speciesMetadata.setSeasonalPatterns(newPatterns)
834 emit MetadataUpdated(field: "seasonalPatterns", oldValue: oldPatterns, newValue: newPatterns)
835 }
836
837 access(all) fun updateRecordWeight(newRecord: UFix64) {
838 let oldRecord = SnookCoin.speciesMetadata.recordWeight?.toString() ?? "0.0"
839 SnookCoin.speciesMetadata.setRecordWeight(newRecord)
840 emit MetadataUpdated(field: "recordWeight", oldValue: oldRecord, newValue: newRecord.toString())
841 }
842
843 access(all) fun updateRecordLength(newRecord: UFix64) {
844 let oldRecord = SnookCoin.speciesMetadata.recordLength?.toString() ?? "0.0"
845 SnookCoin.speciesMetadata.setRecordLength(newRecord)
846 emit MetadataUpdated(field: "recordLength", oldValue: oldRecord, newValue: newRecord.toString())
847 }
848
849 // MISSING: Record location and date admin functions
850 access(all) fun updateRecordWeightLocation(newLocation: String?) {
851 let oldLocation = SnookCoin.speciesMetadata.recordWeightLocation ?? ""
852 SnookCoin.speciesMetadata.setRecordWeightLocation(newLocation)
853 emit MetadataUpdated(field: "recordWeightLocation", oldValue: oldLocation, newValue: newLocation ?? "")
854 }
855
856 access(all) fun updateRecordWeightDate(newDate: String?) {
857 let oldDate = SnookCoin.speciesMetadata.recordWeightDate ?? ""
858 SnookCoin.speciesMetadata.setRecordWeightDate(newDate)
859 emit MetadataUpdated(field: "recordWeightDate", oldValue: oldDate, newValue: newDate ?? "")
860 }
861
862 access(all) fun updateRecordLengthLocation(newLocation: String?) {
863 let oldLocation = SnookCoin.speciesMetadata.recordLengthLocation ?? ""
864 SnookCoin.speciesMetadata.setRecordLengthLocation(newLocation)
865 emit MetadataUpdated(field: "recordLengthLocation", oldValue: oldLocation, newValue: newLocation ?? "")
866 }
867
868 access(all) fun updateRecordLengthDate(newDate: String?) {
869 let oldDate = SnookCoin.speciesMetadata.recordLengthDate ?? ""
870 SnookCoin.speciesMetadata.setRecordLengthDate(newDate)
871 emit MetadataUpdated(field: "recordLengthDate", oldValue: oldDate, newValue: newDate ?? "")
872 }
873
874 // MISSING: Convenience function to update complete records
875 access(all) fun updateCompleteWeightRecord(weight: UFix64?, location: String?, date: String?) {
876 if let w = weight { self.updateRecordWeight(newRecord: w) }
877 self.updateRecordWeightLocation(newLocation: location)
878 self.updateRecordWeightDate(newDate: date)
879 }
880
881 access(all) fun updateCompleteLengthRecord(length: UFix64?, location: String?, date: String?) {
882 if let l = length { self.updateRecordLength(newRecord: l) }
883 self.updateRecordLengthLocation(newLocation: location)
884 self.updateRecordLengthDate(newDate: date)
885 }
886
887 // MISSING: Community data curation approval
888 access(all) fun approvePendingUpdate(index: Int) {
889 pre {
890 index >= 0 && index < SnookCoin.pendingUpdates.length: "Invalid update index"
891 }
892 let update = SnookCoin.pendingUpdates.remove(at: index)
893 // Apply the update based on field type
894 emit MetadataUpdated(field: update.field, oldValue: "pending", newValue: update.newValue)
895 }
896
897 access(all) fun rejectPendingUpdate(index: Int) {
898 pre {
899 index >= 0 && index < SnookCoin.pendingUpdates.length: "Invalid update index"
900 }
901 SnookCoin.pendingUpdates.remove(at: index)
902 }
903
904 access(all) fun clearAllPendingUpdates() {
905 SnookCoin.pendingUpdates = []
906 }
907
908 // ENHANCED ADMIN FUNCTIONS WITH VALIDATION
909 access(all) fun updateRarityTierValidated(newTier: UInt8) {
910 pre {
911 SnookCoin.validateRating(newTier): "Invalid rarity tier (must be 1-10)"
912 }
913 let oldTier = SnookCoin.speciesMetadata.rarityTier?.toString() ?? "0"
914 SnookCoin.speciesMetadata.setRarityTier(newTier)
915 emit MetadataUpdated(field: "rarityTier", oldValue: oldTier, newValue: newTier.toString())
916 }
917
918 access(all) fun updateConservationStatusValidated(newStatus: String) {
919 pre {
920 SnookCoin.validateConservationStatus(newStatus): "Invalid conservation status"
921 }
922 let oldStatus = SnookCoin.speciesMetadata.globalConservationStatus ?? ""
923 SnookCoin.speciesMetadata.setConservationStatus(newStatus)
924 emit MetadataUpdated(field: "globalConservationStatus", oldValue: oldStatus, newValue: newStatus)
925 }
926
927 access(all) fun updateFightRatingValidated(newRating: UInt8) {
928 pre {
929 SnookCoin.validateRating(newRating): "Fight rating must be 1-10"
930 }
931 let oldRating = SnookCoin.speciesMetadata.fightRating?.toString() ?? "0"
932 SnookCoin.speciesMetadata.setFightRating(newRating)
933 emit MetadataUpdated(field: "fightRating", oldValue: oldRating, newValue: newRating.toString())
934 }
935
936 // TEMPORAL ADMIN FUNCTIONS
937 access(all) fun archiveCurrentYear() {
938 let currentYear = SnookCoin.currentMetadataYear
939 SnookCoin.metadataHistory[currentYear] = SnookCoin.speciesMetadata
940 emit YearlyMetadataCreated(year: currentYear)
941 }
942
943 access(all) fun updateToNewYear(_ newYear: UInt64) {
944 pre {
945 newYear > SnookCoin.currentMetadataYear: "New year must be greater than current year"
946 }
947 // Archive current year data
948 self.archiveCurrentYear()
949
950 let oldYear = SnookCoin.currentMetadataYear
951 SnookCoin.currentMetadataYear = newYear
952 emit MetadataYearUpdated(oldYear: oldYear, newYear: newYear)
953 }
954 }
955
956 // Public functions
957 access(all) fun createEmptyVault(vaultType: Type): @SnookCoin.Vault {
958 pre {
959 vaultType == Type<@SnookCoin.Vault>(): "Vault type mismatch"
960 }
961 return <-create Vault(balance: 0.0)
962 }
963
964 access(all) view fun getSpeciesMetadata(): SpeciesMetadata {
965 return self.speciesMetadata
966 }
967
968 // TEMPORAL METADATA MANAGEMENT - Track changes over time
969 access(all) view fun getMetadataForYear(_ year: UInt64): SpeciesMetadata? {
970 return self.metadataHistory[year]
971 }
972
973 access(all) view fun getAvailableYears(): [UInt64] {
974 return self.metadataHistory.keys
975 }
976
977 access(all) fun createYearlyMetadata(_ year: UInt64) {
978 pre {
979 self.metadataHistory[year] == nil: "Metadata for this year already exists"
980 }
981 // Create a copy of current metadata for the new year
982 let newMetadata = self.speciesMetadata
983 self.metadataHistory[year] = newMetadata
984 emit YearlyMetadataCreated(year: year)
985 }
986
987 // REGIONAL DATA MANAGEMENT - Handle location-specific information
988 access(all) fun addRegionalPopulation(_ region: String, _ data: RegionalPopulation) {
989 self.speciesMetadata.regionalPopulations[region] = data
990 }
991
992 access(all) fun addRegionalRegulation(_ region: String, _ data: RegionalRegulations) {
993 self.speciesMetadata.regionalRegulations[region] = data
994 }
995
996 access(all) view fun getRegionalPrice(_ region: String): UFix64? {
997 return self.speciesMetadata.regionalCommercialValue[region]
998 }
999
1000 // SIMPLE SPECIES-LEVEL ANALYTICS - Building blocks for FishDEX
1001 access(all) view fun isEndangered(): Bool {
1002 if let status = self.speciesMetadata.globalConservationStatus {
1003 return status == "Endangered" || status == "Critical" || status == "Critically Endangered"
1004 }
1005 return false
1006 }
1007
1008 access(all) view fun getConservationTier(): UInt8 {
1009 // Simple 1-5 scale based on conservation status (for trading algorithms)
1010 if let status = self.speciesMetadata.globalConservationStatus {
1011 switch status {
1012 case "Least Concern": return 1
1013 case "Near Threatened": return 2
1014 case "Vulnerable": return 3
1015 case "Endangered": return 4
1016 case "Critically Endangered": return 5
1017 default: return 3
1018 }
1019 }
1020 return 3
1021 }
1022
1023 // FISH NFT INTEGRATION HOOKS - Ready for when Fish NFTs are implemented
1024 access(all) view fun getSpeciesInfo(): {String: AnyStruct} {
1025 // Standard interface Fish NFTs can call to get species data
1026 return {
1027 "speciesCode": self.speciesMetadata.speciesCode,
1028 "ticker": self.speciesMetadata.ticker,
1029 "commonName": self.speciesMetadata.commonName,
1030 "scientificName": self.speciesMetadata.scientificName,
1031 "conservationTier": self.getConservationTier(),
1032 "totalSupply": self.totalSupply
1033 }
1034 }
1035
1036 access(all) fun recordCatchForSpecies(fishNFTId: UInt64, catchData: {String: AnyStruct}) {
1037 // Called by Fish NFT contract when a catch is verified
1038 // This will trigger species coin minting
1039 let angler = catchData["angler"] as? Address ?? self.account.address
1040 emit CatchVerified(fishId: fishNFTId, angler: angler, amount: 1.0)
1041 }
1042
1043 access(all) view fun getCatchCount(): UInt64 {
1044 // Total verified catches = total supply (1 coin per catch)
1045 return UInt64(self.totalSupply)
1046 }
1047
1048 // BAITCOIN EXCHANGE INTEGRATION - Dual-token economy
1049 access(all) var baitExchangeRate: UFix64? // Species coin → BaitCoin conversion rate
1050
1051 access(all) view fun getBaitExchangeRate(): UFix64? {
1052 return self.baitExchangeRate
1053 }
1054
1055 access(all) fun updateBaitExchangeRate(_ rate: UFix64) {
1056 // Only admin can set exchange rates
1057 let oldRate = self.baitExchangeRate?.toString() ?? "0.0"
1058 self.baitExchangeRate = rate
1059 emit MetadataUpdated(field: "baitExchangeRate", oldValue: oldRate, newValue: rate.toString())
1060 }
1061
1062 // INPUT VALIDATION - Prevent bad data
1063 access(all) view fun validateRating(_ rating: UInt8): Bool {
1064 return rating >= 1 && rating <= 10
1065 }
1066
1067 access(all) view fun validateConservationStatus(_ status: String): Bool {
1068 let validStatuses = ["Least Concern", "Near Threatened", "Vulnerable", "Endangered", "Critically Endangered", "Extinct", "Stable", "Threatened", "Critical"]
1069 return validStatuses.contains(status)
1070 }
1071
1072 // COMMUNITY DATA CURATION - Simplified for future growth
1073 access(all) struct DataUpdate {
1074 access(all) let field: String
1075 access(all) let newValue: String
1076 access(all) let contributor: Address
1077 access(all) let source: String
1078 access(all) let timestamp: UFix64
1079
1080 init(field: String, newValue: String, contributor: Address, source: String) {
1081 self.field = field
1082 self.newValue = newValue
1083 self.contributor = contributor
1084 self.source = source
1085 self.timestamp = getCurrentBlock().timestamp
1086 }
1087 }
1088
1089 access(all) var pendingUpdates: [DataUpdate]
1090
1091 access(all) fun submitDataUpdate(field: String, value: String, source: String) {
1092 let update = DataUpdate(
1093 field: field,
1094 newValue: value,
1095 contributor: self.account.address,
1096 source: source
1097 )
1098 self.pendingUpdates.append(update)
1099 }
1100
1101 // BATCH OPERATIONS - Scientific data import
1102 access(all) fun updateMetadataBatch(updates: {String: String}) {
1103 // Admin can update multiple fields at once
1104 for field in updates.keys {
1105 let value = updates[field]!
1106 // Apply validation and update metadata
1107 emit MetadataUpdated(field: field, oldValue: "batch_old", newValue: value)
1108 }
1109 }
1110
1111 access(all) fun addMultipleRegions(
1112 populations: {String: RegionalPopulation},
1113 regulations: {String: RegionalRegulations}
1114 ) {
1115 // Add population data for multiple regions
1116 for region in populations.keys {
1117 self.speciesMetadata.regionalPopulations[region] = populations[region]!
1118 }
1119
1120 // Add regulatory data for multiple regions
1121 for region in regulations.keys {
1122 self.speciesMetadata.regionalRegulations[region] = regulations[region]!
1123 }
1124 }
1125
1126 // FISHDEX QUERY HELPERS - Trading decision support
1127 access(all) view fun getRegionsWithData(): [String] {
1128 return self.speciesMetadata.regionalPopulations.keys
1129 }
1130
1131 access(all) view fun hasCompleteMetadata(): Bool {
1132 // Check if core fields are populated
1133 return self.speciesMetadata.habitat != nil &&
1134 self.speciesMetadata.averageWeight != nil &&
1135 self.speciesMetadata.globalConservationStatus != nil &&
1136 self.speciesMetadata.regionalPopulations.length > 0
1137 }
1138
1139 access(all) view fun getDataCompleteness(): UInt8 {
1140 // Return 1-10 score of how complete the species data is
1141 var score: UInt8 = 0
1142
1143 // Core biological data (3 points)
1144 if self.speciesMetadata.habitat != nil { score = score + 1 }
1145 if self.speciesMetadata.averageWeight != nil { score = score + 1 }
1146 if self.speciesMetadata.lifespan != nil { score = score + 1 }
1147
1148 // Conservation data (2 points)
1149 if self.speciesMetadata.globalConservationStatus != nil { score = score + 1 }
1150 if self.speciesMetadata.regionalPopulations.length > 0 { score = score + 1 }
1151
1152 // Regional data (2 points)
1153 if self.speciesMetadata.regionalRegulations.length > 0 { score = score + 1 }
1154 if self.speciesMetadata.regionalCommercialValue.length > 0 { score = score + 1 }
1155
1156 // Records & research (3 points)
1157 if self.speciesMetadata.recordWeight != nil { score = score + 1 }
1158 if self.speciesMetadata.studyPrograms.length > 0 { score = score + 1 }
1159 if self.speciesMetadata.firstCatchDate != nil { score = score + 1 }
1160
1161 return score
1162 }
1163
1164 // MISSING: Public query functions for practical interaction
1165 access(all) view fun getBasicInfo(): {String: AnyStruct} {
1166 return {
1167 "speciesCode": self.speciesMetadata.speciesCode,
1168 "ticker": self.speciesMetadata.ticker,
1169 "commonName": self.speciesMetadata.commonName,
1170 "scientificName": self.speciesMetadata.scientificName,
1171 "family": self.speciesMetadata.family,
1172 "totalSupply": self.totalSupply,
1173 "description": self.speciesMetadata.description
1174 }
1175 }
1176
1177 access(all) view fun getRegionalInfo(region: String): {String: AnyStruct?} {
1178 return {
1179 "population": self.speciesMetadata.regionalPopulations[region],
1180 "regulations": self.speciesMetadata.regionalRegulations[region],
1181 "commercialValue": self.speciesMetadata.regionalCommercialValue[region]
1182 }
1183 }
1184
1185 access(all) view fun getRecordInfo(): {String: AnyStruct?} {
1186 return {
1187 "recordWeight": self.speciesMetadata.recordWeight,
1188 "recordWeightLocation": self.speciesMetadata.recordWeightLocation,
1189 "recordWeightDate": self.speciesMetadata.recordWeightDate,
1190 "recordLength": self.speciesMetadata.recordLength,
1191 "recordLengthLocation": self.speciesMetadata.recordLengthLocation,
1192 "recordLengthDate": self.speciesMetadata.recordLengthDate
1193 }
1194 }
1195
1196 access(all) view fun getAnglingInfo(): {String: AnyStruct?} {
1197 return {
1198 "bestBaits": self.speciesMetadata.bestBaits,
1199 "bestTechniques": self.speciesMetadata.bestTechniques,
1200 "fightRating": self.speciesMetadata.fightRating,
1201 "culinaryRating": self.speciesMetadata.culinaryRating,
1202 "catchDifficulty": self.speciesMetadata.catchDifficulty,
1203 "seasonalAvailability": self.speciesMetadata.seasonalAvailability
1204 }
1205 }
1206
1207 access(all) view fun getConservationInfo(): {String: AnyStruct?} {
1208 return {
1209 "conservationStatus": self.speciesMetadata.globalConservationStatus,
1210 "conservationTier": self.getConservationTier(),
1211 "isEndangered": self.isEndangered(),
1212 "nativeRegions": self.speciesMetadata.nativeRegions,
1213 "currentRange": self.speciesMetadata.currentRange,
1214 "invasiveStatus": self.speciesMetadata.invasiveStatus
1215 }
1216 }
1217
1218 access(all) view fun getBiologicalInfo(): {String: AnyStruct?} {
1219 return {
1220 "lifespan": self.speciesMetadata.lifespan,
1221 "diet": self.speciesMetadata.diet,
1222 "predators": self.speciesMetadata.predators,
1223 "temperatureRange": self.speciesMetadata.temperatureRange,
1224 "depthRange": self.speciesMetadata.depthRange,
1225 "spawningAge": self.speciesMetadata.spawningAge,
1226 "spawningBehavior": self.speciesMetadata.spawningBehavior,
1227 "migrationPattern": self.speciesMetadata.migrationPattern
1228 }
1229 }
1230
1231 access(all) view fun getPendingUpdates(): [DataUpdate] {
1232 return self.pendingUpdates
1233 }
1234
1235 access(all) view fun getPendingUpdateCount(): Int {
1236 return self.pendingUpdates.length
1237 }
1238
1239 // MISSING: Temporal query functions
1240 access(all) view fun getCurrentYear(): UInt64 {
1241 return self.currentMetadataYear
1242 }
1243
1244 access(all) view fun hasHistoricalData(year: UInt64): Bool {
1245 return self.metadataHistory[year] != nil
1246 }
1247
1248 access(all) view fun getYearlyDataSummary(): {UInt64: String} {
1249 let summary: {UInt64: String} = {}
1250 for year in self.metadataHistory.keys {
1251 let metadata = self.metadataHistory[year]!
1252 summary[year] = metadata.commonName.concat(" - ").concat(metadata.description.slice(from: 0, upTo: 50))
1253 }
1254 return summary
1255 }
1256
1257 // FISHDEX INTEGRATION - Public query functions
1258 access(all) view fun getFishDEXAddress(): Address? {
1259 return self.fishDEXAddress
1260 }
1261
1262 access(all) view fun getFishDEXRegistrationStatus(): Bool {
1263 return self.isRegisteredWithFishDEX
1264 }
1265
1266 access(all) view fun getRegistryInfo(): {String: AnyStruct} {
1267 return {
1268 "speciesCode": self.speciesMetadata.speciesCode,
1269 "ticker": self.speciesMetadata.ticker,
1270 "commonName": self.speciesMetadata.commonName,
1271 "scientificName": self.speciesMetadata.scientificName,
1272 "family": self.speciesMetadata.family,
1273 "totalSupply": self.totalSupply,
1274 "contractAddress": self.account.address,
1275 "fishDEXAddress": self.fishDEXAddress,
1276 "isRegistered": self.isRegisteredWithFishDEX,
1277 "dataYear": self.speciesMetadata.dataYear
1278 }
1279 }
1280
1281 // Create public capability for FishDEX coordinator (called during account setup)
1282 access(all) fun createFishDEXCoordinatorCapability(): Capability<&FishDEXCoordinator> {
1283 return self.account.capabilities.storage.issue<&FishDEXCoordinator>(self.FishDEXCoordinatorStoragePath)
1284 }
1285
1286 // Essential token interaction functions
1287 access(all) view fun getTotalSupply(): UFix64 {
1288 return self.totalSupply
1289 }
1290
1291 // NOTE: Snook coins represent permanent historical catch records
1292 // No burning functions provided - total supply always equals total verified catches
1293 // Only natural vault destruction (via burnCallback) affects individual balances
1294
1295 // MISSING: Token utility functions
1296 access(all) view fun getVaultBalance(vaultRef: &SnookCoin.Vault): UFix64 {
1297 return vaultRef.balance
1298 }
1299
1300 access(all) view fun canWithdraw(vaultRef: &SnookCoin.Vault, amount: UFix64): Bool {
1301 return vaultRef.isAvailableToWithdraw(amount: amount)
1302 }
1303
1304 // MISSING: Supply analytics for trading
1305 access(all) view fun getSupplyMetrics(): {String: UFix64} {
1306 return {
1307 "totalSupply": self.totalSupply,
1308 "totalCatches": self.totalSupply, // 1 coin per catch
1309 "circulatingSupply": self.totalSupply // Assuming all minted coins are in circulation
1310 }
1311 }
1312
1313 // MISSING: Token validation
1314 access(all) view fun isValidVaultType(vaultType: Type): Bool {
1315 return vaultType == Type<@SnookCoin.Vault>()
1316 }
1317
1318 // Contract initialization
1319 init() {
1320 self.totalSupply = 0.0
1321
1322 // Initialize NFT tracking
1323 self.fishNFTRegistry = {}
1324
1325 // Initialize temporal metadata system
1326 self.currentMetadataYear = 2024
1327 self.metadataHistory = {}
1328 self.defaultRegion = "Global"
1329
1330 // Initialize BaitCoin exchange rate (nil until set by admin)
1331 self.baitExchangeRate = nil
1332
1333 // Initialize community curation system
1334 self.pendingUpdates = []
1335
1336 // Initialize FishDEX integration
1337 self.fishDEXAddress = nil
1338 self.isRegisteredWithFishDEX = false
1339
1340 // Set comprehensive species metadata for Example Fish
1341 self.speciesMetadata = SpeciesMetadata(
1342 // IMMUTABLE CORE FIELDS
1343 speciesCode: "CENTROPOMUS_UNDECIMALIS",
1344 ticker: "CENUND",
1345 scientificName: "Centropomus undecimalis",
1346 family: "Centropomidae",
1347 dataYear: 2024,
1348
1349 // BASIC DESCRIPTIVE FIELDS
1350 commonName: "Common Snook",
1351 habitat: "Coastal waters, estuaries, mangroves, rivers, canals",
1352 averageWeight: 8.0,
1353 averageLength: 24.0,
1354 imageURL: "https://derby.fish/images/species/Snook.jpg",
1355 description: "Snook are prized tropical gamefish known for their aggressive strikes and excellent table fare. They have a distinctive lateral line and prefer warm coastal waters with structure.",
1356 firstCatchDate: nil,
1357 rarityTier: 3,
1358
1359 // CONSERVATION & POPULATION
1360 globalConservationStatus: "Least Concern",
1361 regionalPopulations: {
1362 "Florida East Coast": RegionalPopulation(
1363 populationTrend: "Stable",
1364 threats: ["Cold Snaps", "Habitat Loss", "Overfishing"],
1365 protectedAreas: ["Everglades National Park", "Biscayne National Park"],
1366 estimatedPopulation: nil
1367 ),
1368 "Florida West Coast": RegionalPopulation(
1369 populationTrend: "Stable",
1370 threats: ["Red Tide", "Habitat Modification", "Temperature Fluctuations"],
1371 protectedAreas: ["Tampa Bay Aquatic Preserve", "Charlotte Harbor"],
1372 estimatedPopulation: nil
1373 ),
1374 "Texas Coast": RegionalPopulation(
1375 populationTrend: "Stable",
1376 threats: ["Cold Weather", "Habitat Loss", "Water Quality"],
1377 protectedAreas: ["Laguna Madre", "State Parks"],
1378 estimatedPopulation: nil
1379 )
1380 },
1381
1382 // BIOLOGICAL INTELLIGENCE
1383 lifespan: 21.0,
1384 diet: "Fish, shrimp, crabs, mullet, pilchards, pinfish",
1385 predators: ["Sharks", "Tarpon", "Larger fish", "Birds", "Humans"],
1386 temperatureRange: "68-90°F (optimal 75-85°F)",
1387 depthRange: "0-60 feet",
1388 spawningAge: 3.0,
1389 spawningBehavior: "Spawns in estuaries and coastal waters, protandric hermaphrodite",
1390 migrationPattern: "Moves between saltwater and freshwater, temperature-dependent",
1391 waterQualityNeeds: "Warm water, variable salinity tolerance, good dissolved oxygen",
1392
1393 // GEOGRAPHIC & HABITAT
1394 nativeRegions: ["Florida", "Gulf Coast", "Caribbean", "Central America"],
1395 currentRange: ["Florida", "Texas", "Louisiana", "South Carolina"],
1396 waterTypes: ["Estuarine", "Coastal", "Mangrove", "River", "Canal"],
1397 invasiveStatus: "Native",
1398
1399 // ECONOMIC & COMMERCIAL
1400 regionalCommercialValue: {
1401 "Florida": 15.00,
1402 "Texas": 12.00,
1403 "Louisiana": 10.00,
1404 "South Carolina": 14.00
1405 },
1406 tourismValue: 9,
1407 ecosystemRole: "Apex Predator (inshore waters), Keystone Species",
1408 culturalSignificance: "Iconic Florida gamefish, major tourist attraction",
1409
1410 // ANGLING & RECREATIONAL
1411 bestBaits: ["Live pilchards", "Shrimp", "Mullet", "Pinfish", "Artificial lures"],
1412 fightRating: 8,
1413 culinaryRating: 9,
1414 catchDifficulty: 6,
1415 seasonalAvailability: "Year-round in south Florida, seasonal in northern range",
1416 bestTechniques: ["Live bait fishing", "Casting lures", "Trolling", "Sight fishing", "Bridge fishing"],
1417
1418 // REGULATORY
1419 regionalRegulations: {
1420 "Florida": RegionalRegulations(
1421 sizeLimit: 28.0,
1422 bagLimit: 1,
1423 closedSeasons: ["June 1 - August 31", "December 15 - January 31"],
1424 specialRegulations: "Snook permit required, no harvest during spawning",
1425 licenseRequired: true
1426 ),
1427 "Texas": RegionalRegulations(
1428 sizeLimit: 15.0,
1429 bagLimit: 5,
1430 closedSeasons: [],
1431 specialRegulations: "No closed season",
1432 licenseRequired: true
1433 ),
1434 "Louisiana": RegionalRegulations(
1435 sizeLimit: 12.0,
1436 bagLimit: 25,
1437 closedSeasons: [],
1438 specialRegulations: "Basic saltwater license",
1439 licenseRequired: true
1440 )
1441 },
1442
1443 // PHYSICAL & BEHAVIORAL
1444 physicalDescription: "Silver-green body with prominent lateral line, large mouth, protruding jaw, forked tail",
1445 behaviorTraits: "Ambush predator, structure-oriented, temperature sensitive, aggressive feeder",
1446 seasonalPatterns: "Spawning in summer, winter congregation in warm water refuges",
1447
1448 // RECORDS & ACHIEVEMENTS
1449 recordWeight: 53.1,
1450 recordWeightLocation: "Parismina Ranch, Costa Rica",
1451 recordWeightDate: "October 18, 2014",
1452 recordLength: 47.6,
1453 recordLengthLocation: "Stuart, Florida",
1454 recordLengthDate: "September 3, 2011",
1455
1456 // RESEARCH & SCIENTIFIC
1457 researchPriority: 9,
1458 geneticMarkers: "Population genetics and stock structure studies",
1459 studyPrograms: ["Florida Fish and Wildlife Conservation Commission", "Texas Parks and Wildlife", "Gulf Coast Research"],
1460
1461 // FLEXIBLE METADATA
1462 additionalMetadata: {
1463 "last_updated": "2024-01-01",
1464 "data_quality": "High",
1465 "contributor": "DerbyFish Research Team",
1466 "state_fish": "Florida (unofficial)",
1467 "nickname": "Linesider"
1468 }
1469 )
1470
1471 // Set storage paths using common name format
1472 self.VaultStoragePath = /storage/SnookCoinVault
1473 self.VaultPublicPath = /public/SnookCoinReceiver
1474 self.MinterStoragePath = /storage/SnookCoinMinter
1475 self.MetadataAdminStoragePath = /storage/SnookCoinMetadataAdmin
1476 self.FishDEXCoordinatorStoragePath = /storage/SnookCoinFishDEXCoordinator
1477 self.FishDEXCoordinatorPublicPath = /public/SnookCoinFishDEXCoordinator
1478
1479 // Create and store admin resources
1480 let minter <- create Minter()
1481 self.account.storage.save(<-minter, to: self.MinterStoragePath)
1482
1483 let metadataAdmin <- create MetadataAdmin()
1484 self.account.storage.save(<-metadataAdmin, to: self.MetadataAdminStoragePath)
1485
1486 // Create and store FishDEX coordinator
1487 let fishDEXCoordinator <- create FishDEXCoordinator()
1488 self.account.storage.save(<-fishDEXCoordinator, to: self.FishDEXCoordinatorStoragePath)
1489
1490 // Create public capability for FishDEX coordinator
1491 let coordinatorCapability = self.account.capabilities.storage.issue<&FishDEXCoordinator>(self.FishDEXCoordinatorStoragePath)
1492 self.account.capabilities.publish(coordinatorCapability, at: self.FishDEXCoordinatorPublicPath)
1493
1494 let vault <- create Vault(balance: self.totalSupply)
1495 self.account.storage.save(<-vault, to: self.VaultStoragePath)
1496 let cap = self.account.capabilities.storage.issue<&SnookCoin.Vault>(self.VaultStoragePath)
1497 self.account.capabilities.publish(cap, at: self.VaultPublicPath)
1498 }
1499}