Smart Contract
BluefinTunaCoin
A.44100f14f70e3f78.BluefinTunaCoin
1import FungibleToken from 0xf233dcee88fe0abe
2import MetadataViews from 0x1d7e57aa55817448
3import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
4
5access(all) contract BluefinTunaCoin: 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): @BluefinTunaCoin.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<&BluefinTunaCoin.Vault>(),
500 metadataLinkedType: Type<&BluefinTunaCoin.Vault>(),
501 createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
502 return <-BluefinTunaCoin.createEmptyVault(vaultType: Type<@BluefinTunaCoin.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 BluefinTunaCoin.speciesMetadata.speciesCode
517 }
518
519 access(all) view fun getTicker(): String {
520 return BluefinTunaCoin.speciesMetadata.ticker
521 }
522
523 access(all) view fun getCommonName(): String {
524 return BluefinTunaCoin.speciesMetadata.commonName
525 }
526
527 access(all) view fun getScientificName(): String {
528 return BluefinTunaCoin.speciesMetadata.scientificName
529 }
530
531 access(all) view fun getFamily(): String {
532 return BluefinTunaCoin.speciesMetadata.family
533 }
534
535 access(all) view fun getTotalSupply(): UFix64 {
536 return BluefinTunaCoin.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": BluefinTunaCoin.account.address,
548 "dataYear": BluefinTunaCoin.speciesMetadata.dataYear,
549 "conservationStatus": BluefinTunaCoin.speciesMetadata.globalConservationStatus,
550 "rarityTier": BluefinTunaCoin.speciesMetadata.rarityTier,
551 "isRegistered": BluefinTunaCoin.isRegisteredWithFishDEX
552 }
553 }
554
555 // Process catch verification from Fish NFT contracts
556 access(all) fun redeemCatchNFT(fishData: {String: AnyStruct}, angler: Address): @BluefinTunaCoin.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 BluefinTunaCoin.fishNFTRegistry[nftId] == nil,
572 message: "This NFT has already been redeemed for a coin"
573 )
574 // Record this NFT as redeemed
575 BluefinTunaCoin.fishNFTRegistry[nftId] = true
576 }
577
578 // Auto-record first catch if this is the very first mint
579 if BluefinTunaCoin.totalSupply == 0.0 && BluefinTunaCoin.speciesMetadata.firstCatchDate == nil {
580 BluefinTunaCoin.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 BluefinTunaCoin.totalSupply = BluefinTunaCoin.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 !BluefinTunaCoin.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 BluefinTunaCoin.fishDEXAddress = fishDEXAddress
617 BluefinTunaCoin.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 = BluefinTunaCoin.fishDEXAddress
625 BluefinTunaCoin.fishDEXAddress = newAddress
626 BluefinTunaCoin.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 BluefinTunaCoin.totalSupply = BluefinTunaCoin.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 BluefinTunaCoin.getContractViews(resourceType: nil)
649 }
650
651 access(all) fun resolveView(_ view: Type): AnyStruct? {
652 return BluefinTunaCoin.resolveContractView(resourceType: nil, viewType: view)
653 }
654
655 access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
656 return {Type<@BluefinTunaCoin.Vault>(): true}
657 }
658
659 access(all) view fun isSupportedVaultType(type: Type): Bool {
660 return type == Type<@BluefinTunaCoin.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): @BluefinTunaCoin.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! @BluefinTunaCoin.Vault
674 self.balance = self.balance + vault.balance
675 vault.balance = 0.0
676 destroy vault
677 }
678
679 access(all) fun createEmptyVault(): @BluefinTunaCoin.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): @BluefinTunaCoin.Vault {
688 pre {
689 amount == 1.0: "Only 1 coin per verified catch"
690 BluefinTunaCoin.fishNFTRegistry[fishId] == nil: "This NFT has already been used to mint a coin"
691 }
692
693 // Record this NFT as used
694 BluefinTunaCoin.fishNFTRegistry[fishId] = true
695
696 // Auto-record first catch if this is the very first mint
697 if BluefinTunaCoin.totalSupply == 0.0 && BluefinTunaCoin.speciesMetadata.firstCatchDate == nil {
698 BluefinTunaCoin.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 BluefinTunaCoin.totalSupply = BluefinTunaCoin.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 = BluefinTunaCoin.fishDEXAddress {
716 if !BluefinTunaCoin.isRegisteredWithFishDEX {
717 // Get reference to FishDEX coordinator
718 if let coordinatorRef = BluefinTunaCoin.account.capabilities.borrow<&FishDEXCoordinator>(
719 BluefinTunaCoin.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 BluefinTunaCoin.fishDEXAddress = fishDEXAddress
730 self.autoRegisterWithFishDEX()
731 }
732
733 access(all) fun mintBatch(recipients: {Address: UFix64}): @{Address: BluefinTunaCoin.Vault} {
734 let vaults: @{Address: BluefinTunaCoin.Vault} <- {}
735
736 for recipient in recipients.keys {
737 let amount = recipients[recipient]!
738 BluefinTunaCoin.totalSupply = BluefinTunaCoin.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 = BluefinTunaCoin.speciesMetadata.imageURL ?? ""
756 BluefinTunaCoin.speciesMetadata.setImageURL(newURL)
757 emit MetadataUpdated(field: "imageURL", oldValue: oldURL, newValue: newURL)
758 }
759
760 access(all) fun updateDescription(newDescription: String) {
761 let oldDescription = BluefinTunaCoin.speciesMetadata.description
762 BluefinTunaCoin.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 = BluefinTunaCoin.speciesMetadata.habitat ?? ""
770 BluefinTunaCoin.speciesMetadata.setHabitat(newHabitat)
771 emit MetadataUpdated(field: "habitat", oldValue: oldHabitat, newValue: newHabitat)
772 }
773
774 access(all) fun updateAverageWeight(newWeight: UFix64) {
775 let oldWeight = BluefinTunaCoin.speciesMetadata.averageWeight?.toString() ?? "0.0"
776 BluefinTunaCoin.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 = BluefinTunaCoin.speciesMetadata.averageLength?.toString() ?? "0.0"
782 BluefinTunaCoin.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 = BluefinTunaCoin.speciesMetadata.rarityTier?.toString() ?? "0"
791 BluefinTunaCoin.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 BluefinTunaCoin.speciesMetadata.firstCatchDate == nil: "First catch already recorded"
798 }
799 BluefinTunaCoin.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 = BluefinTunaCoin.speciesMetadata.globalConservationStatus ?? ""
807 BluefinTunaCoin.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 BluefinTunaCoin.speciesMetadata.nativeRegions {
814 if i > 0 { oldRegionsStr = oldRegionsStr.concat(",") }
815 oldRegionsStr = oldRegionsStr.concat(region)
816 }
817 oldRegionsStr = oldRegionsStr.concat("]")
818
819 BluefinTunaCoin.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 = BluefinTunaCoin.speciesMetadata.seasonalPatterns ?? ""
833 BluefinTunaCoin.speciesMetadata.setSeasonalPatterns(newPatterns)
834 emit MetadataUpdated(field: "seasonalPatterns", oldValue: oldPatterns, newValue: newPatterns)
835 }
836
837 access(all) fun updateRecordWeight(newRecord: UFix64) {
838 let oldRecord = BluefinTunaCoin.speciesMetadata.recordWeight?.toString() ?? "0.0"
839 BluefinTunaCoin.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 = BluefinTunaCoin.speciesMetadata.recordLength?.toString() ?? "0.0"
845 BluefinTunaCoin.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 = BluefinTunaCoin.speciesMetadata.recordWeightLocation ?? ""
852 BluefinTunaCoin.speciesMetadata.setRecordWeightLocation(newLocation)
853 emit MetadataUpdated(field: "recordWeightLocation", oldValue: oldLocation, newValue: newLocation ?? "")
854 }
855
856 access(all) fun updateRecordWeightDate(newDate: String?) {
857 let oldDate = BluefinTunaCoin.speciesMetadata.recordWeightDate ?? ""
858 BluefinTunaCoin.speciesMetadata.setRecordWeightDate(newDate)
859 emit MetadataUpdated(field: "recordWeightDate", oldValue: oldDate, newValue: newDate ?? "")
860 }
861
862 access(all) fun updateRecordLengthLocation(newLocation: String?) {
863 let oldLocation = BluefinTunaCoin.speciesMetadata.recordLengthLocation ?? ""
864 BluefinTunaCoin.speciesMetadata.setRecordLengthLocation(newLocation)
865 emit MetadataUpdated(field: "recordLengthLocation", oldValue: oldLocation, newValue: newLocation ?? "")
866 }
867
868 access(all) fun updateRecordLengthDate(newDate: String?) {
869 let oldDate = BluefinTunaCoin.speciesMetadata.recordLengthDate ?? ""
870 BluefinTunaCoin.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 < BluefinTunaCoin.pendingUpdates.length: "Invalid update index"
891 }
892 let update = BluefinTunaCoin.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 < BluefinTunaCoin.pendingUpdates.length: "Invalid update index"
900 }
901 BluefinTunaCoin.pendingUpdates.remove(at: index)
902 }
903
904 access(all) fun clearAllPendingUpdates() {
905 BluefinTunaCoin.pendingUpdates = []
906 }
907
908 // ENHANCED ADMIN FUNCTIONS WITH VALIDATION
909 access(all) fun updateRarityTierValidated(newTier: UInt8) {
910 pre {
911 BluefinTunaCoin.validateRating(newTier): "Invalid rarity tier (must be 1-10)"
912 }
913 let oldTier = BluefinTunaCoin.speciesMetadata.rarityTier?.toString() ?? "0"
914 BluefinTunaCoin.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 BluefinTunaCoin.validateConservationStatus(newStatus): "Invalid conservation status"
921 }
922 let oldStatus = BluefinTunaCoin.speciesMetadata.globalConservationStatus ?? ""
923 BluefinTunaCoin.speciesMetadata.setConservationStatus(newStatus)
924 emit MetadataUpdated(field: "globalConservationStatus", oldValue: oldStatus, newValue: newStatus)
925 }
926
927 access(all) fun updateFightRatingValidated(newRating: UInt8) {
928 pre {
929 BluefinTunaCoin.validateRating(newRating): "Fight rating must be 1-10"
930 }
931 let oldRating = BluefinTunaCoin.speciesMetadata.fightRating?.toString() ?? "0"
932 BluefinTunaCoin.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 = BluefinTunaCoin.currentMetadataYear
939 BluefinTunaCoin.metadataHistory[currentYear] = BluefinTunaCoin.speciesMetadata
940 emit YearlyMetadataCreated(year: currentYear)
941 }
942
943 access(all) fun updateToNewYear(_ newYear: UInt64) {
944 pre {
945 newYear > BluefinTunaCoin.currentMetadataYear: "New year must be greater than current year"
946 }
947 // Archive current year data
948 self.archiveCurrentYear()
949
950 let oldYear = BluefinTunaCoin.currentMetadataYear
951 BluefinTunaCoin.currentMetadataYear = newYear
952 emit MetadataYearUpdated(oldYear: oldYear, newYear: newYear)
953 }
954 }
955
956 // Public functions
957 access(all) fun createEmptyVault(vaultType: Type): @BluefinTunaCoin.Vault {
958 pre {
959 vaultType == Type<@BluefinTunaCoin.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: BluefinTuna 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: &BluefinTunaCoin.Vault): UFix64 {
1297 return vaultRef.balance
1298 }
1299
1300 access(all) view fun canWithdraw(vaultRef: &BluefinTunaCoin.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<@BluefinTunaCoin.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: "THUNNUS_THYNNUS",
1344 ticker: "THUTHY",
1345 scientificName: "Thunnus thynnus",
1346 family: "Scombridae",
1347 dataYear: 2024,
1348
1349 // BASIC DESCRIPTIVE FIELDS
1350 commonName: "Bluefin Tuna",
1351 habitat: "Open ocean, temperate and tropical waters",
1352 averageWeight: 550.0,
1353 averageLength: 78.0,
1354 imageURL: "https://derby.fish/images/species/BluefinTuna.jpg",
1355 description: "Bluefin Tuna are the giants of the BluefinTuna family, capable of reaching enormous sizes and incredible speeds. They are among the most valuable fish in the world, prized for their rich, fatty meat in sushi and sashimi markets. These powerful predators are warm-blooded and can regulate their body temperature.",
1356 firstCatchDate: nil,
1357 rarityTier: 5,
1358
1359 // CONSERVATION & POPULATION
1360 globalConservationStatus: "Critically Endangered",
1361 regionalPopulations: {
1362 "North Atlantic": RegionalPopulation(
1363 populationTrend: "Declining",
1364 threats: ["Overfishing", "Climate Change", "Illegal Fishing"],
1365 protectedAreas: ["Marine Protected Areas", "Fisheries Closure Areas"],
1366 estimatedPopulation: nil
1367 ),
1368 "Mediterranean": RegionalPopulation(
1369 populationTrend: "Critically Declining",
1370 threats: ["Overfishing", "Ranching Operations", "Habitat Loss"],
1371 protectedAreas: ["Mediterranean MPAs", "Spawning Sanctuaries"],
1372 estimatedPopulation: nil
1373 ),
1374 "Pacific": RegionalPopulation(
1375 populationTrend: "Stable",
1376 threats: ["Commercial Fishing", "Longline Bycatch", "Climate Change"],
1377 protectedAreas: ["Pacific Remote Islands Marine National Monument"],
1378 estimatedPopulation: nil
1379 )
1380 },
1381
1382 // BIOLOGICAL INTELLIGENCE
1383 lifespan: 40.0,
1384 diet: "Mackerel, herring, sardines, squid, crustaceans, flying fish",
1385 predators: ["Large Sharks", "Killer Whales", "Humans"],
1386 temperatureRange: "50-82°F (can thermoregulate)",
1387 depthRange: "0-3000 feet (highly migratory)",
1388 spawningAge: 8.0,
1389 spawningBehavior: "Spawns in warm waters of Gulf of Mexico and Mediterranean, pelagic eggs",
1390 migrationPattern: "Highly migratory across ocean basins, follows temperature gradients and food sources",
1391 waterQualityNeeds: "Clean, well-oxygenated saltwater, prefers deep blue water",
1392
1393 // GEOGRAPHIC & HABITAT
1394 nativeRegions: ["North Atlantic", "Mediterranean Sea", "North Pacific"],
1395 currentRange: ["Atlantic Ocean", "Mediterranean Sea", "Pacific Ocean"],
1396 waterTypes: ["Open Ocean", "Pelagic", "Continental Shelf", "Deep Water"],
1397 invasiveStatus: "Native",
1398
1399 // ECONOMIC & COMMERCIAL
1400 regionalCommercialValue: {
1401 "Japan": 200.00,
1402 "Mediterranean": 150.00,
1403 "US East Coast": 180.00,
1404 "Atlantic Canada": 120.00
1405 },
1406 tourismValue: 10,
1407 ecosystemRole: "Apex Predator, Key Pelagic Species",
1408 culturalSignificance: "Central to Japanese cuisine culture, historically important to Mediterranean fishing communities",
1409
1410 // ANGLING & RECREATIONAL
1411 bestBaits: ["Live mackerel", "Chunked bait", "Large lures", "Butterfly jigs", "Cedar plugs"],
1412 fightRating: 10,
1413 culinaryRating: 10,
1414 catchDifficulty: 10,
1415 seasonalAvailability: "Seasonal migrations, best summer-fall in temperate waters",
1416 bestTechniques: ["Chunking", "Trolling", "Live bait fishing", "Butterfly jigging", "Kite fishing"],
1417
1418 // REGULATORY
1419 regionalRegulations: {
1420 "US Atlantic": RegionalRegulations(
1421 sizeLimit: 73.0,
1422 bagLimit: 1,
1423 closedSeasons: ["January 1 - June 1"],
1424 specialRegulations: "Highly regulated quota system, HMS permit required",
1425 licenseRequired: true
1426 ),
1427 "Mediterranean": RegionalRegulations(
1428 sizeLimit: 30.0,
1429 bagLimit: nil,
1430 closedSeasons: ["November 16 - December 15"],
1431 specialRegulations: "ICCAT quotas, catch documentation required",
1432 licenseRequired: true
1433 ),
1434 "Pacific": RegionalRegulations(
1435 sizeLimit: nil,
1436 bagLimit: 2,
1437 closedSeasons: [],
1438 specialRegulations: "Varies by region, some areas closed to fishing",
1439 licenseRequired: true
1440 )
1441 },
1442
1443 // PHYSICAL & BEHAVIORAL
1444 physicalDescription: "Massive, torpedo-shaped body with metallic blue-black back, silver sides, powerful crescent tail, large head with big eyes",
1445 behaviorTraits: "Warm-blooded, schooling behavior, incredibly fast (up to 43 mph), deep diving capability",
1446 seasonalPatterns: "Complex migrations across ocean basins, spawning in specific warm-water areas",
1447
1448 // RECORDS & ACHIEVEMENTS
1449 recordWeight: 1496.0,
1450 recordWeightLocation: "Nova Scotia, Canada",
1451 recordWeightDate: "October 26, 1979",
1452 recordLength: 144.0,
1453 recordLengthLocation: "Prince Edward Island, Canada",
1454 recordLengthDate: "September 1988",
1455
1456 // RESEARCH & SCIENTIFIC
1457 researchPriority: 10,
1458 geneticMarkers: "Extensive genetic studies for stock assessment and population structure",
1459 studyPrograms: ["ICCAT Research", "NOAA Bluefin BluefinTuna Research", "Stanford BluefinTuna Research Program"],
1460
1461 // FLEXIBLE METADATA
1462 additionalMetadata: {
1463 "last_updated": "2024-01-01",
1464 "data_quality": "High",
1465 "contributor": "DerbyFish Research Team",
1466 "max_speed": "43 mph",
1467 "nickname": "Giant Bluefin"
1468 }
1469 )
1470
1471 // Set storage paths using common name format
1472 self.VaultStoragePath = /storage/BluefinTunaCoinVault
1473 self.VaultPublicPath = /public/BluefinTunaCoinReceiver
1474 self.MinterStoragePath = /storage/BluefinTunaCoinMinter
1475 self.MetadataAdminStoragePath = /storage/BluefinTunaCoinMetadataAdmin
1476 self.FishDEXCoordinatorStoragePath = /storage/BluefinTunaCoinFishDEXCoordinator
1477 self.FishDEXCoordinatorPublicPath = /public/BluefinTunaCoinFishDEXCoordinator
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<&BluefinTunaCoin.Vault>(self.VaultStoragePath)
1497 self.account.capabilities.publish(cap, at: self.VaultPublicPath)
1498 }
1499}