Smart Contract

AllDay

A.e4cf4bdc1751c65d.AllDay

Valid From

143,304,748

Deployed

2w ago
Feb 11, 2026, 05:25:35 PM UTC

Dependents

315235 imports
1/*
2    Adapted from: Genies.cdc
3    Author: Rhea Myers rhea.myers@dapperlabs.com
4    Author: Sadie Freeman sadie.freeman@dapperlabs.com
5*/
6
7import NonFungibleToken from 0x1d7e57aa55817448
8import FungibleToken from 0xf233dcee88fe0abe
9import MetadataViews from 0x1d7e57aa55817448
10import ViewResolver from 0x1d7e57aa55817448
11
12/*
13    AllDay is structured similarly to Genies and TopShot.
14    Unlike TopShot, we use resources for all entities and manage access to their data
15    by copying it to structs (this simplifies access control, in particular write access).
16    We also encapsulate resource creation for the admin in member functions on the parent type.
17
18    There are 5 levels of entity:
19    1. Series
20    2. Sets
21    3. Plays
22    4. Editions
23    4. Moment NFT (an NFT)
24
25    An Edition is created with a combination of a Series, Set, and Play
26    Moment NFTs are minted out of Editions.
27
28    Note that we cache some information (Series names/ids, counts of entities) rather
29    than calculate it each time.
30    This is enabled by encapsulation and saves gas for entity lifecycle operations.
31 */
32
33// The AllDay NFTs and metadata contract
34//
35access(all) contract AllDay: NonFungibleToken {
36    //------------------------------------------------------------
37    // Events
38    //------------------------------------------------------------
39
40    // Contract Events
41    //
42    access(all) event ContractInitialized()
43
44    // NFT Collection Events
45    //
46    access(all) event Withdraw(id: UInt64, from: Address?)
47    access(all) event Deposit(id: UInt64, to: Address?)
48
49    // Series Events
50    //
51    // Emitted when a new series has been created by an admin
52    access(all) event SeriesCreated(id: UInt64, name: String)
53    // Emitted when a series is closed by an admin
54    access(all) event SeriesClosed(id: UInt64)
55
56    // Set Events
57    //
58    // Emitted when a new set has been created by an admin
59    access(all) event SetCreated(id: UInt64, name: String)
60
61    // Play Events
62    //
63    // Emitted when a new play has been created by an admin
64    access(all) event PlayCreated(id: UInt64, classification: String, metadata: {String: String})
65
66    // Edition Events
67    //
68    // Emitted when a new edition has been created by an admin
69    access(all) event EditionCreated(
70        id: UInt64,
71        seriesID: UInt64,
72        setID: UInt64,
73        playID: UInt64,
74        maxMintSize: UInt64?,
75        tier: String,
76        parallel: String
77    )
78    // Emitted when an edition is either closed by an admin, or the max amount of moments have been minted
79    access(all) event EditionClosed(id: UInt64)
80
81    // NFT Events
82    //
83    access(all) event MomentNFTMinted(id: UInt64, editionID: UInt64, serialNumber: UInt64)
84    access(all) event MomentNFTBurned(id: UInt64)
85
86    // Badges Events
87    //
88    access(all) event BadgeCreated(slug: String, title: String, description: String, visible: Bool, slugV2: String, metadata: {String: String})
89    access(all) event BadgeUpdated(slug: String, title: String, description: String, visible: Bool, slugV2: String, metadata: {String: String})
90    access(all) event BadgeAddedToEntity(badgeSlug: String, entityType: String, entityID: UInt64, metadata: {String: String})
91    access(all) event BadgeRemovedFromEntity(badgeSlug: String, entityType: String, entityID: UInt64)
92    access(all) event BadgeDeleted(slug: String)
93
94    //------------------------------------------------------------
95    // Named values
96    //------------------------------------------------------------
97
98    // Named Paths
99    //
100    access(all) let CollectionStoragePath:  StoragePath
101    access(all) let CollectionPublicPath:   PublicPath
102    access(all) let AdminStoragePath:       StoragePath
103
104    //------------------------------------------------------------
105    // Publicly readable contract state
106    //------------------------------------------------------------
107
108    // Entity Counts
109    //
110    access(all) var totalSupply:        UInt64
111    access(all) var nextSeriesID:       UInt64
112    access(all) var nextSetID:          UInt64
113    access(all) var nextPlayID:         UInt64
114    access(all) var nextEditionID:      UInt64
115
116    //------------------------------------------------------------
117    // Internal contract state
118    //------------------------------------------------------------
119
120    // Metadata Dictionaries
121    //
122    // This is so we can find Series by their names (via seriesByID)
123    access(self) let seriesIDByName:    {String: UInt64}
124    access(self) let seriesByID:        @{UInt64: Series}
125    access(self) let setIDByName:       {String: UInt64}
126    access(self) let setByID:           @{UInt64: Set}
127    access(self) let playByID:          @{UInt64: Play}
128    access(self) let editionByID:       @{UInt64: Edition}
129
130    //------------------------------------------------------------
131    // Series
132    //------------------------------------------------------------
133
134    // A public struct to access Series data
135    //
136    access(all) struct SeriesData {
137        access(all) let id: UInt64
138        access(all) let name: String
139        access(all) let active: Bool
140
141        // initializer
142        //
143        view init (id: UInt64) {
144            if let series = &AllDay.seriesByID[id] as &AllDay.Series? {
145                self.id = series.id
146                self.name = series.name
147                self.active = series.active
148            } else {
149                panic("series does not exist")
150            }
151        }
152    }
153
154    // A top-level Series with a unique ID and name
155    //
156    access(all) resource Series {
157        access(all) let id: UInt64
158        access(all) let name: String
159        access(all) var active: Bool
160
161        // Close this series
162        //
163        access(all) fun close() {
164            pre {
165                self.active == true: "not active"
166            }
167
168            self.active = false
169
170            emit SeriesClosed(id: self.id)
171        }
172
173        // initializer
174        //
175        init (name: String) {
176            pre {
177                !AllDay.seriesIDByName.containsKey(name): "A Series with that name already exists"
178            }
179            self.id = AllDay.nextSeriesID
180            self.name = name
181            self.active = true
182
183            // Cache the new series's name => ID
184            AllDay.seriesIDByName[name] = self.id
185            // Increment for the nextSeriesID
186            AllDay.nextSeriesID = self.id + 1 as UInt64
187
188            emit SeriesCreated(id: self.id, name: self.name)
189        }
190    }
191
192    // Get the publicly available data for a Series by id
193    //
194    access(all) view fun getSeriesData(id: UInt64): AllDay.SeriesData {
195        pre {
196            AllDay.seriesByID[id] != nil: "Cannot borrow series, no such id"
197        }
198
199        return AllDay.SeriesData(id: id)
200    }
201
202    // Get the publicly available data for a Series by name
203    //
204    access(all) view fun getSeriesDataByName(name: String): AllDay.SeriesData {
205        pre {
206            AllDay.seriesIDByName[name] != nil: "Cannot borrow series, no such name"
207        }
208
209        let id = AllDay.seriesIDByName[name]!
210
211        return AllDay.SeriesData(id: id)
212    }
213
214    // Get all series names (this will be *long*)
215    //
216    access(all) view fun getAllSeriesNames(): [String] {
217        return AllDay.seriesIDByName.keys
218    }
219
220    // Get series id for name
221    //
222    access(all) view fun getSeriesIDByName(name: String): UInt64? {
223        return AllDay.seriesIDByName[name]
224    }
225
226    //------------------------------------------------------------
227    // Set
228    //------------------------------------------------------------
229
230    // A public struct to access Set data
231    //
232    access(all) struct SetData {
233        access(all) let id: UInt64
234        access(all) let name: String
235        access(all) var setPlaysInEditions: &{UInt64: Bool}
236
237        // member function to check the setPlaysInEditions to see if this Set/Play combination already exists
238        access(all) view fun setPlayExistsInEdition(playID: UInt64): Bool {
239           return self.setPlaysInEditions.containsKey(playID)
240        }
241
242        // initializer
243        //
244        view init (id: UInt64) {
245            if let set = &AllDay.setByID[id] as &AllDay.Set? {
246            self.id = id
247            self.name = set.name
248            self.setPlaysInEditions = set.setPlaysInEditions
249            } else {
250               panic("set does not exist")
251            }
252        }
253    }
254
255    // A top level Set with a unique ID and a name
256    //
257    access(all) resource Set {
258        access(all) let id: UInt64
259        access(all) let name: String
260        // Store a dictionary of all the Plays which are paired with the Set inside Editions
261        // This enforces only one Set/Play unique pair can be used for an Edition
262        access(all) var setPlaysInEditions: {UInt64: Bool}
263
264        // member function to insert a new Play to the setPlaysInEditions dictionary
265        access(all) fun insertNewPlay(playID: UInt64) {
266            self.setPlaysInEditions[playID] = true
267        }
268
269        // initializer
270        //
271        init (name: String) {
272            pre {
273                !AllDay.setIDByName.containsKey(name): "A Set with that name already exists"
274            }
275            self.id = AllDay.nextSetID
276            self.name = name
277            self.setPlaysInEditions = {}
278
279            // Cache the new set's name => ID
280            AllDay.setIDByName[name] = self.id
281            // Increment for the nextSeriesID
282            AllDay.nextSetID = self.id + 1 as UInt64
283
284            emit SetCreated(id: self.id, name: self.name)
285        }
286    }
287
288    // Get the publicly available data for a Set
289    //
290    access(all) view fun getSetData(id: UInt64): AllDay.SetData {
291        pre {
292            AllDay.setByID[id] != nil: "Cannot borrow set, no such id"
293        }
294
295        return AllDay.SetData(id: id)
296    }
297
298    // Get the publicly available data for a Set by name
299    //
300    access(all) view fun getSetDataByName(name: String): AllDay.SetData {
301        pre {
302            AllDay.setIDByName[name] != nil: "Cannot borrow set, no such name"
303        }
304
305        let id = AllDay.setIDByName[name]!
306
307        return AllDay.SetData(id: id)
308    }
309
310    // Get all set names (this will be *long*)
311    //
312    access(all) view fun getAllSetNames(): [String] {
313        return AllDay.setIDByName.keys
314    }
315
316
317    //------------------------------------------------------------
318    // Play
319    //------------------------------------------------------------
320
321    // A public struct to access Play data
322    //
323    access(all) struct PlayData {
324        access(all) let id: UInt64
325        access(all) let classification: String
326        access(all) let metadata: &{String: String}
327
328        // initializer
329        //
330        view init (id: UInt64) {
331            if let play = &AllDay.playByID[id] as &AllDay.Play? {
332            self.id = id
333            self.classification = play.classification
334            self.metadata = play.metadata
335            } else {
336                panic("play does not exist")
337            }
338        }
339    }
340
341    // A top level Play with a unique ID and a classification
342    //
343    access(all) resource Play {
344        access(all) let id: UInt64
345        access(all) let classification: String
346        // Contents writable if borrowed!
347        // This is deliberate, as it allows admins to update the data.
348        access(all) let metadata: {String: String}
349
350        // initializer
351        //
352        init (classification: String, metadata: {String: String}) {
353            self.id = AllDay.nextPlayID
354            self.classification = classification
355            self.metadata = metadata
356
357            AllDay.nextPlayID = self.id + 1 as UInt64
358
359            emit PlayCreated(id: self.id, classification: self.classification, metadata: self.metadata)
360        }
361
362        access(contract) fun updateDescription(description: String) {
363            self.metadata["description"] = description
364        }
365
366        access(contract) fun updateDynamicMetadata(optTeamName: String?, optPlayerFirstName: String?,
367            optPlayerLastName: String?, optPlayerNumber: String?, optPlayerPosition: String?) {
368            if let teamName = optTeamName {
369                self.metadata["teamName"] = teamName
370            }
371            if let playerFirstName = optPlayerFirstName {
372                self.metadata["playerFirstName"] = playerFirstName
373            }
374            if let playerLastName = optPlayerLastName {
375                self.metadata["playerLastName"] = playerLastName
376            }
377            if let playerNumber = optPlayerNumber {
378                self.metadata["playerNumber"] = playerNumber
379            }
380            if let playerPosition = optPlayerPosition {
381                self.metadata["playerPosition"] = playerPosition
382            }
383        }
384    }
385
386    // Get the publicly available data for a Play
387    //
388    access(all) view fun getPlayData(id: UInt64): AllDay.PlayData {
389        pre {
390            AllDay.playByID[id] != nil: "Cannot borrow play, no such id"
391        }
392
393        return AllDay.PlayData(id: id)
394    }
395
396    //------------------------------------------------------------
397    // Edition
398    //------------------------------------------------------------
399
400    // A public struct to access Edition data
401    //
402    access(all) struct EditionData {
403        access(all) let id: UInt64
404        access(all) let seriesID: UInt64
405        access(all) let setID: UInt64
406        access(all) let playID: UInt64
407        access(all) var maxMintSize: UInt64?
408        access(all) let tier: String
409        access(all) var numMinted: UInt64
410
411       // member function to check if max edition size has been reached
412       access(all) view fun maxEditionMintSizeReached(): Bool {
413            return self.numMinted == self.maxMintSize
414        }
415
416        access(all) view fun getParallel(): String {
417            return AllDay.getParallelForEdition(self.id)
418        }
419
420        // initializer
421        //
422        view init (id: UInt64) {
423           if let edition = &AllDay.editionByID[id] as &AllDay.Edition? {
424            self.id = id
425            self.seriesID = edition.seriesID
426            self.playID = edition.playID
427            self.setID = edition.setID
428            self.maxMintSize = edition.maxMintSize
429            self.tier = edition.tier
430            self.numMinted = edition.numMinted
431           } else {
432               panic("edition does not exist")
433           }
434        }
435    }
436
437    // A top level Edition that contains a Series, Set, and Play
438    //
439    access(all) resource Edition {
440        access(all) let id: UInt64
441        access(all) let seriesID: UInt64
442        access(all) let setID: UInt64
443        access(all) let playID: UInt64
444        access(all) let tier: String
445        // Null value indicates that there is unlimited minting potential for the Edition
446        access(all) var maxMintSize: UInt64?
447        // Updates each time we mint a new moment for the Edition to keep a running total
448        access(all) var numMinted: UInt64
449
450        // Close this edition so that no more Moment NFTs can be minted in it
451        //
452        access(contract) fun close() {
453            pre {
454                self.numMinted != self.maxMintSize: "max number of minted moments has already been reached"
455            }
456
457            self.maxMintSize = self.numMinted
458
459            emit EditionClosed(id: self.id)
460        }
461
462        // Mint a Moment NFT in this edition, with the given minting mintingDate.
463        // Note that this will panic if the max mint size has already been reached.
464        //
465        access(all) fun mint(serialNumber: UInt64?): @AllDay.NFT {
466            pre {
467                self.numMinted != self.maxMintSize: "max number of minted moments has been reached"
468            }
469
470            var serial = self.numMinted + 1 as UInt64
471
472            // Create the Moment NFT, filled out with our information
473            let momentNFT <- create NFT(
474                id: AllDay.totalSupply + 1,
475                editionID: self.id,
476                serialNumber: serial
477            )
478            AllDay.totalSupply = AllDay.totalSupply + 1
479            // Keep a running total (you'll notice we used this as the serial number for closed editions)
480            self.numMinted = self.numMinted + 1 as UInt64
481
482            return <- momentNFT
483        }
484
485        // Get this edition's parallel
486        access(all) view fun getParallel(): String {
487            return AllDay.getParallelForEdition(self.id)
488        }
489
490        // initializer
491        //
492        init (
493            seriesID: UInt64,
494            setID: UInt64,
495            playID: UInt64,
496            maxMintSize: UInt64?,
497            tier: String,
498            parallel: String?
499        ) {
500            pre {
501                maxMintSize != 0: "max mint size is zero, must either be null or greater than 0"
502                AllDay.seriesByID.containsKey(seriesID): "seriesID does not exist"
503                AllDay.setByID.containsKey(setID): "setID does not exist"
504                AllDay.playByID.containsKey(playID): "playID does not exist"
505                SeriesData(id: seriesID).active == true: "cannot create an Edition with a closed Series"
506                AllDay.getPlayTierParallelExistsInEdition(setID, playID, tier, parallel) == false: "set play tier combination already exists in an edition"
507                AllDay.isValidTier(tier): "tier is not a valid tier"
508                AllDay.isValidParallel(parallel): "parallel is not a valid parallel"
509            }
510
511            self.id = AllDay.nextEditionID
512            self.seriesID = seriesID
513            self.setID = setID
514            self.playID = playID
515
516            // If an edition size is not set, it has unlimited minting potential
517            if maxMintSize == 0 {
518                self.maxMintSize = nil
519            } else {
520                self.maxMintSize = maxMintSize
521            }
522
523            self.tier = tier
524
525            // Store parallel for the edition if specified
526            if parallel != nil {
527                var addOnsResource = AllDay.borrowAddOns()
528                if addOnsResource == nil {
529                    AllDay.initializeAddOnsInStorage()
530                    addOnsResource = AllDay.borrowAddOns()
531                }
532                addOnsResource!.insertParallelDataForEdition(editionID: self.id, parallel: parallel!)
533            }
534
535            self.numMinted = 0 as UInt64
536
537            AllDay.nextEditionID = AllDay.nextEditionID + 1 as UInt64
538            AllDay.setByID[setID]?.insertNewPlay(playID: playID)
539            AllDay.insertSetPlayTierMap(setID, playID, tier, parallel)
540
541            emit EditionCreated(
542                id: self.id,
543                seriesID: self.seriesID,
544                setID: self.setID,
545                playID: self.playID,
546                maxMintSize: self.maxMintSize,
547                tier: self.tier,
548                parallel: self.getParallel(),
549            )
550        }
551    }
552
553    // Check if parallel is valid
554    //
555    access(all) view fun isValidParallel(_ parallel: String?): Bool {
556        return parallel == nil || {"Ruby": true, "Emerald": true, "Sapphire": true, "Opal": true, "Diamond": true, "Obsidian": true, "Topaz": true}.containsKey(parallel!)
557    }
558
559    // Check if tier is valid
560    //
561    access(all) view fun isValidTier(_ tier: String): Bool {
562        return {"COMMON": true, "UNCOMMON": true, "LEGENDARY": true, "RARE": true, "ULTIMATE": true}.containsKey(tier)
563    }
564
565    // Get the publicly available data for an Edition
566    //
567    access(all) view fun getEditionData(id: UInt64): EditionData {
568        pre {
569            AllDay.editionByID[id] != nil: "Cannot borrow edition, no such id"
570        }
571
572        return AllDay.EditionData(id: id)
573    }
574
575    //------------------------------------------------------------
576    // Internal functions for tracking Editions minted with Set + Play + Tier combinations
577    //------------------------------------------------------------
578
579    // Get storage path for SetPlayTierMap
580    //
581    access(contract) view fun getSetPlayTierMapStorage(): StoragePath {
582        return /storage/AllDayAdminSetPlayTierMap
583    }
584
585    // Get composite key used to read/write SetPlayTierMap
586    //
587    access(contract) view fun getSetPlayTierParallelMapKey(_ setID: UInt64,_ playID: UInt64,_ tier: String, _ parallel: String?): String {
588        var s = setID.toString().concat("-").concat(playID.toString()).concat("-").concat(tier)
589        if parallel != nil {
590            s = s.concat("-").concat(parallel!)
591        }
592        return s
593    }
594
595    // Check if the given set, play, tier has already been minted in an Edition
596    //
597    access(contract) view fun getPlayTierParallelExistsInEdition(_ setID: UInt64, _ playID: UInt64, _ tier: String, _ parallel: String?): Bool {
598        let setPlayTierMap = AllDay.account.storage.borrow<&{String: Bool}>(from: AllDay.getSetPlayTierMapStorage())!
599        return setPlayTierMap.containsKey(AllDay.getSetPlayTierParallelMapKey(setID, playID, tier, parallel))
600    }
601
602    // Insert new entry into SetPlayTierMap
603    //
604    access(contract) fun insertSetPlayTierMap(_ setID: UInt64, _ playID: UInt64, _ tier: String, _ parallel: String?) {
605        let setPlayTierMap = AllDay.account.storage.load<{String: Bool}>(from: AllDay.getSetPlayTierMapStorage())!
606        setPlayTierMap.insert(key: AllDay.getSetPlayTierParallelMapKey(setID, playID, tier, parallel), true)
607        AllDay.account.storage.save(setPlayTierMap, to: /storage/AllDayAdminSetPlayTierMap)
608    }
609
610    //------------------------------------------------------------
611    // Badges
612    //------------------------------------------------------------  
613
614    // Enum for valid entity types that can have badges
615    access(all) enum BadgeEntityType: UInt8 {
616        access(all) case play
617        access(all) case edition
618        access(all) case moment
619    }
620
621    // Helper function to convert BadgeEntityType enum to string
622    access(all) fun badgeEntityTypeToString(_ entityType: BadgeEntityType): String {
623        switch entityType {
624            case BadgeEntityType.play:
625                return "play"
626            case BadgeEntityType.edition:
627                return "edition"
628            case BadgeEntityType.moment:
629                return "moment"
630        }
631        return ""
632    }
633
634    access(all) struct Badge{
635        access(all) var slug: String
636        access(all) var title: String
637        access(all) var description: String
638        access(all) var visible: Bool
639        access(all) var slugV2: String
640        access(all) var metadata: {String: String}
641
642        view init(slug: String, title: String, description: String, visible: Bool, slugV2: String){
643            self.slug = slug
644            self.title = title
645            self.description = description
646            self.visible = visible
647            self.slugV2 = slugV2
648            self.metadata = {}
649        }
650
651        access(all) fun update(title: String?, description: String?, visible: Bool?, slugV2: String?, metadata: {String: String}?){
652            if title != nil{
653                self.title = title!
654            }
655            if description != nil{
656                self.description = description!
657            }
658            if visible != nil{
659                self.visible = visible!
660            }
661            if slugV2 != nil{
662                self.slugV2 = slugV2!
663            }
664            if metadata != nil{
665                self.metadata = metadata!
666            }
667        }
668    }
669
670    //------------------------------------------------------------
671    // Parallels
672    //------------------------------------------------------------
673
674    access(all) struct ParallelData {
675        access(all) let parallel: String
676        access(all) let extension: {String: AnyStruct}
677
678        view init(parallel: String) {
679            self.parallel = parallel
680            self.extension = {}
681        }
682    }
683
684    access(all) resource AddOns {
685        access(self) let slugToBadge: {String: Badge}
686        access(self) let playIdToBadgeSlugs: {UInt64: {String: {String: String}}}
687        access(self) let editionIdToBadgeSlugs: {UInt64: {String: {String: String}}}
688        access(self) let momentIdToBadgeSlugs: {UInt64: {String: {String: String}}}
689        access(self) let editionIdToParallelData: {UInt64: ParallelData}
690        access(self) let extension:{String: {String: AnyStruct}}
691
692        // Badges initializer
693        //
694        view init() {
695            self.slugToBadge = {}
696            self.playIdToBadgeSlugs = {}
697            self.editionIdToBadgeSlugs = {}
698            self.momentIdToBadgeSlugs = {}
699            self.editionIdToParallelData = {}
700            self.extension = {}
701        }
702
703        access(contract) view fun getParallelDataForEdition(_ editionID: UInt64): ParallelData? {
704            return self.editionIdToParallelData[editionID]
705        }
706
707        access(contract) fun insertParallelDataForEdition(editionID: UInt64, parallel: String) {
708            pre {
709                !self.editionIdToParallelData.containsKey(editionID): "parallel already exists for this edition"
710            }
711            self.editionIdToParallelData.insert(key: editionID, ParallelData(parallel: parallel))
712        }
713
714        access(contract) view fun getBadge(_ slug: String): Badge?{
715            return self.slugToBadge[slug]
716        }
717
718        access(contract) fun getPlayBadges(_ playID: UInt64): [Badge]?{
719            if self.playIdToBadgeSlugs[playID] == nil {
720                return nil
721            }
722            let slugs = self.playIdToBadgeSlugs[playID]!
723            var badges: [Badge] = []
724            for slug in slugs.keys{
725                if let badge: Badge = self.slugToBadge[slug]{
726                    badges.append(badge)
727                }
728            }
729            return badges
730        }
731
732        access(contract) fun getEditionBadges(_ editionID: UInt64): [Badge]?{
733            if self.editionIdToBadgeSlugs[editionID] == nil {
734                return nil
735            }
736            let slugs = self.editionIdToBadgeSlugs[editionID]!
737            var badges: [Badge] = []
738            for slug in slugs.keys{
739                if let badge: Badge = self.slugToBadge[slug]{
740                    badges.append(badge)
741                }
742            }
743            return badges
744        }
745
746        access(contract) fun getMomentBadges(_ momentID: UInt64): [Badge]?{
747            if self.momentIdToBadgeSlugs[momentID] == nil {
748                return nil
749            }
750            let slugs = self.momentIdToBadgeSlugs[momentID]!
751            var badges: [Badge] = []
752            for slug in slugs.keys{
753                if let badge: Badge = self.slugToBadge[slug]{
754                    badges.append(badge)
755                }
756            }
757            return badges
758        }
759
760        access(contract) fun createBadge(slug: String, title: String, description: String, visible: Bool, slugV2: String){
761            assert(self.slugToBadge[slug] == nil, message: "badge already exists")
762            let badge = Badge(slug: slug, title: title, description: description, visible: visible, slugV2: slugV2)
763            self.slugToBadge[slug] = badge
764            emit BadgeCreated(slug: slug, title: title, description: description, visible: visible, slugV2: slugV2, metadata: badge.metadata)
765        }
766
767        access(contract) fun updateBadge(slug: String, title: String?, description: String?, visible: Bool?, slugV2: String?, metadata: {String: String}?){
768            assert(self.slugToBadge[slug] != nil, message: "badge doesn't exist")
769            
770            // Get a reference to the badge in the dictionary and update it directly
771            let badgeRef = &self.slugToBadge[slug] as &Badge?
772            if let badgeRef = badgeRef {
773                badgeRef.update(title: title, description: description, visible: visible, slugV2: slugV2, metadata: metadata)
774                emit BadgeUpdated(slug: slug, title: badgeRef.title, description: badgeRef.description, visible: badgeRef.visible, slugV2: badgeRef.slugV2, metadata: *badgeRef.metadata)
775            }
776        }
777
778        access(contract) fun addBadgeToEntity(badgeSlug: String, entityType: BadgeEntityType, entityID: UInt64, metadata: {String: String}){
779            assert(self.slugToBadge[badgeSlug] != nil, message: "badge doesn't exist")
780            
781            // Get the appropriate dictionary and initialize if needed
782            switch entityType {
783                case BadgeEntityType.play:
784                    if self.playIdToBadgeSlugs[entityID] == nil {
785                        self.playIdToBadgeSlugs[entityID] = {}
786                    }
787                    let playBadgeSlugsRef = &self.playIdToBadgeSlugs[entityID] as auth(Insert) &{String: {String: String}}?
788                        ?? panic("Could not get a reference to the play's badge slugs")
789                    assert(playBadgeSlugsRef[badgeSlug] == nil, message: "badge slug already added to play")
790                    playBadgeSlugsRef.insert(key: badgeSlug, metadata)
791                    
792                case BadgeEntityType.edition:
793                    if self.editionIdToBadgeSlugs[entityID] == nil {
794                        self.editionIdToBadgeSlugs[entityID] = {}
795                    }
796                    let editionBadgeSlugsRef = &self.editionIdToBadgeSlugs[entityID] as auth(Insert) &{String: {String: String}}?
797                        ?? panic("Could not get a reference to the edition's badge slugs")
798                    assert(editionBadgeSlugsRef[badgeSlug] == nil, message: "badge slug already added to edition")
799                    editionBadgeSlugsRef.insert(key: badgeSlug, metadata)
800                    
801                case BadgeEntityType.moment:
802                    if self.momentIdToBadgeSlugs[entityID] == nil {
803                        self.momentIdToBadgeSlugs[entityID] = {}
804                    }
805                    let momentBadgeSlugsRef = &self.momentIdToBadgeSlugs[entityID] as auth(Insert) &{String: {String: String}}?
806                        ?? panic("Could not get a reference to the moment's badge slugs")
807                    assert(momentBadgeSlugsRef[badgeSlug] == nil, message: "badge slug already added to moment")
808                    momentBadgeSlugsRef.insert(key: badgeSlug, metadata)
809            }
810            
811            emit BadgeAddedToEntity(badgeSlug: badgeSlug, entityType: AllDay.badgeEntityTypeToString(entityType), entityID: entityID, metadata: metadata)
812        }
813
814        // Remove badge from entity
815        access(contract) fun removeBadgeFromEntity(badgeSlug: String, entityType: BadgeEntityType, entityID: UInt64){
816            var removed = false
817            
818            switch entityType {
819                case BadgeEntityType.play:
820                    if let playBadges = &self.playIdToBadgeSlugs[entityID] as auth(Remove) &{String: {String: String}}? {
821                        if playBadges.containsKey(badgeSlug) {
822                            playBadges.remove(key: badgeSlug)
823                            removed = true
824                        }
825                    }
826                case BadgeEntityType.edition:
827                    if let editionBadges = &self.editionIdToBadgeSlugs[entityID] as auth(Remove) &{String: {String: String}}? {
828                        if editionBadges.containsKey(badgeSlug) {
829                            editionBadges.remove(key: badgeSlug)
830                            removed = true
831                        }
832                    }
833                case BadgeEntityType.moment:
834                    if let momentBadges = &self.momentIdToBadgeSlugs[entityID] as auth(Remove) &{String: {String: String}}? {
835                        if momentBadges.containsKey(badgeSlug) {
836                            momentBadges.remove(key: badgeSlug)
837                            removed = true
838                        }
839                    }
840            }
841            
842            if removed {
843                emit BadgeRemovedFromEntity(badgeSlug: badgeSlug, entityType: AllDay.badgeEntityTypeToString(entityType), entityID: entityID)
844            }
845        }
846
847        // Delete badge - removes only the badge definition, not associations to avoid computation limits
848        access(contract) fun deleteBadge(slug: String){
849            assert(self.slugToBadge[slug] != nil, message: "badge doesn't exist")
850            
851            // Remove the badge itself
852            self.slugToBadge.remove(key: slug)
853            emit BadgeDeleted(slug: slug)
854        }
855    }  
856
857    access(contract) view fun getAddOnsStoragePath(): StoragePath{
858        return /storage/AllDayAddOns
859    }
860
861    access(contract) fun initializeAddOnsInStorage(){
862        let path = AllDay.getAddOnsStoragePath()
863        let addons <- create AddOns()
864        self.account.storage.save(<-addons, to: path)
865    }
866
867    access(contract) view fun borrowAddOns(): &AllDay.AddOns?{
868        return AllDay.account.storage.borrow<&AllDay.AddOns>(from: AllDay.getAddOnsStoragePath())
869    }
870
871    // Get the parallel for an edition, returns "Standard" if no parallel is set
872    access(contract) view fun getParallelForEdition(_ editionID: UInt64): String {
873        if let ref = AllDay.borrowAddOns() {
874            if let v = ref.getParallelDataForEdition(editionID) {
875                return v.parallel
876            }
877        }
878        return "Standard"
879    }
880
881    access(all) fun getBadge(_ slug: String): Badge?{
882        let addOnsResource = AllDay.borrowAddOns()
883        if addOnsResource == nil{
884            return nil
885        }
886        return addOnsResource!.getBadge(slug)
887    }
888
889    access(contract) fun getPlayBadges(_ playID: UInt64): [Badge]?{
890        let addOnsResource = AllDay.borrowAddOns()
891        if addOnsResource == nil{
892            return nil
893        }
894        return addOnsResource!.getPlayBadges(playID)
895    }
896
897    access(contract) fun getEditionBadges(_ editionID: UInt64): [Badge]?{
898        let addOnsResource = AllDay.borrowAddOns()
899        if addOnsResource == nil{
900            return nil
901        }
902        return addOnsResource!.getEditionBadges(editionID)
903    }
904
905    access(contract) fun getMomentBadges(_ momentID: UInt64): [Badge]?{
906        let addOnsResource = AllDay.borrowAddOns()
907        if addOnsResource == nil{
908            return nil
909        }
910        return addOnsResource!.getMomentBadges(momentID)
911    }
912
913
914    //------------------------------------------------------------
915    // NFT
916    //------------------------------------------------------------
917
918    // A Moment NFT
919    //
920    access(all) resource NFT: NonFungibleToken.NFT {
921        access(all) let id: UInt64
922        access(all) let editionID: UInt64
923        access(all) let serialNumber: UInt64
924        access(all) let mintingDate: UFix64
925
926        access(all) event ResourceDestroyed(
927            id: UInt64 = self.id,
928            editionID: UInt64 = self.editionID,
929            serialNumber: UInt64 = self.serialNumber,
930            mintingDate: UFix64 = self.mintingDate
931        )
932
933        // NFT initializer
934        //
935        init(
936
937            id: UInt64,
938            editionID: UInt64,
939            serialNumber: UInt64
940        ) {
941            pre {
942                AllDay.editionByID[editionID] != nil: "no such editionID"
943                EditionData(id: editionID).maxEditionMintSizeReached() != true: "max edition size already reached"
944            }
945
946            self.id = id
947            self.editionID = editionID
948            self.serialNumber = serialNumber
949            self.mintingDate = getCurrentBlock().timestamp
950
951            emit MomentNFTMinted(id: self.id, editionID: self.editionID, serialNumber: self.serialNumber)
952        }
953
954        // All supported metadata views for the Moment including the Core NFT Views
955        //
956        access(all) view fun getViews(): [Type] {
957            return [
958                Type<MetadataViews.Display>(),
959                Type<MetadataViews.Editions>(),
960                Type<MetadataViews.ExternalURL>(),
961                Type<MetadataViews.Medias>(),
962                Type<MetadataViews.NFTCollectionData>(),
963                Type<MetadataViews.NFTCollectionDisplay>(),
964                Type<MetadataViews.Royalties>(),
965                Type<MetadataViews.Serial>(),
966                Type<MetadataViews.Traits>()
967            ]
968        }
969
970        access(all) fun resolveView(_ view: Type): AnyStruct? {
971            switch view {
972                case Type<MetadataViews.Display>():
973                    return MetadataViews.Display(
974                        name: self.getName(),
975                        description: self.getDescription(),
976                        thumbnail: MetadataViews.HTTPFile(url: self.getImage(imageType: "image", format: "jpeg", width: 256))
977                    )
978                case Type<MetadataViews.Editions>():
979                    let editionList: [MetadataViews.Edition] = [self.getEditionInfo()]
980                    return MetadataViews.Editions(
981                        editionList
982                    )
983                case Type<MetadataViews.ExternalURL>():
984                    return MetadataViews.ExternalURL("https://nflallday.com/moments/".concat(self.id.toString()))
985                case Type<MetadataViews.Medias>():
986                    return MetadataViews.Medias(
987                        [
988                            MetadataViews.Media(
989                                file: MetadataViews.HTTPFile(url: self.getImage(imageType: "image", format: "jpeg", width: 512)),
990                                mediaType: "image/jpeg"
991                            ),
992                            MetadataViews.Media(
993                                file: MetadataViews.HTTPFile(url: self.getImage(imageType: "image-details", format: "jpeg", width: 512)),
994                                mediaType: "image/jpeg"
995                            ),
996                            MetadataViews.Media(
997                                file: MetadataViews.HTTPFile(url: self.getImage(imageType: "image-logo", format: "jpeg", width: 512)),
998                                mediaType: "image/jpeg"
999                            ),
1000                            MetadataViews.Media(
1001                                file: MetadataViews.HTTPFile(url: self.getImage(imageType: "image-legal", format: "jpeg", width: 512)),
1002                                mediaType: "image/jpeg"
1003                            ),
1004                            MetadataViews.Media(
1005                                file: MetadataViews.HTTPFile(url: self.getImage(imageType: "image-player", format: "jpeg", width: 512)),
1006                                mediaType: "image/jpeg"
1007                            ),
1008                            MetadataViews.Media(
1009                                file: MetadataViews.HTTPFile(url: self.getImage(imageType: "image-scores", format: "jpeg", width: 512)),
1010                                mediaType: "image/jpeg"
1011                            ),
1012                            MetadataViews.Media(
1013                                file: MetadataViews.HTTPFile(url: self.getVideo(videoType: "video")),
1014                                mediaType: "video/mp4"
1015                            ),
1016                            MetadataViews.Media(
1017                                file: MetadataViews.HTTPFile(url: self.getVideo(videoType: "video-idle")),
1018                                mediaType: "video/mp4"
1019                            )
1020                        ]
1021                    )
1022                case Type<MetadataViews.NFTCollectionData>():
1023                    return AllDay.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionData>())
1024                case Type<MetadataViews.NFTCollectionDisplay>():
1025                    return AllDay.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionDisplay>())
1026                case Type<MetadataViews.Royalties>():
1027                    return AllDay.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.Royalties>())
1028                case Type<MetadataViews.Serial>():
1029                    return MetadataViews.Serial(self.serialNumber)
1030                case Type<MetadataViews.Traits>():
1031                    let excludedNames: [String] = []
1032                    let fullDictionary = self.getTraits()
1033                    return MetadataViews.dictToTraits(dict: fullDictionary, excludedNames: excludedNames)
1034            }
1035            return nil
1036        }
1037
1038        access(all) view fun getName(): String {
1039            let edition: EditionData = AllDay.getEditionData(id: self.editionID)
1040            let play: PlayData = AllDay.getPlayData(id: edition.playID)
1041            let firstName: String = play.metadata["playerFirstName"] ?? ""
1042            let lastName: String = play.metadata["playerLastName"] ?? ""
1043            let playType: String = play.metadata["playType"] ?? ""
1044            return firstName.concat(" ").concat(lastName).concat(" ").concat(playType)
1045        }
1046
1047        access(all) view fun getDescription(): String {
1048            let edition: EditionData = AllDay.getEditionData(id: self.editionID)
1049            let play: PlayData = AllDay.getPlayData(id: edition.playID)
1050            let description: String = play.metadata["description"] ?? ""
1051            if description != "" {
1052                return description
1053            }
1054
1055            let series: SeriesData = AllDay.getSeriesData(id: edition.seriesID)
1056            let set: SetData = AllDay.getSetData(id: edition.setID)
1057            return series.name.concat(" ").concat(set.name).concat(" moment with serial number ").concat(self.serialNumber.toString())
1058        }
1059
1060        access(all) view fun assetPath(): String {
1061            return "https://media.nflallday.com/editions/".concat(self.editionID.toString()).concat("/media/")
1062        }
1063
1064        access(all) view fun getImage(imageType: String, format: String, width: Int): String {
1065            return self.assetPath().concat(imageType).concat("?format=").concat(format).concat("&width=").concat(width.toString())
1066        }
1067
1068        access(all) view fun getVideo(videoType: String): String {
1069            return self.assetPath().concat(videoType)
1070        }
1071
1072        access(all) view fun getMomentURL(): String {
1073            return "https://nflallday.com/moments/".concat(self.id.toString())
1074        }
1075
1076        access(all) fun getBadges(): [Badge]?{
1077            var uniqueBadges: {String: Badge} = {}
1078            
1079            // Add moment badges
1080            if let momentBadges = AllDay.getMomentBadges(self.id){
1081                for badge in momentBadges {
1082                    uniqueBadges[badge.slug] = badge
1083                }
1084            }
1085            
1086            // Add edition badges
1087            if let editionBadges = AllDay.getEditionBadges(self.editionID){
1088                for badge in editionBadges {
1089                    uniqueBadges[badge.slug] = badge
1090                }
1091            }
1092            
1093            // Add play badges
1094            let edition: EditionData = AllDay.getEditionData(id: self.editionID)
1095            if let playBadges = AllDay.getPlayBadges(edition.playID){
1096                for badge in playBadges {
1097                    uniqueBadges[badge.slug] = badge
1098                }
1099            }
1100            
1101            // Convert back to array
1102            if uniqueBadges.length > 0 {
1103                var badges: [Badge] = []
1104                for slug in uniqueBadges.keys {
1105                    badges.append(uniqueBadges[slug]!)
1106                }
1107                return badges
1108            }
1109            return nil
1110        }
1111
1112        access(all) view fun getEditionInfo() : MetadataViews.Edition {
1113            let edition: EditionData = AllDay.getEditionData(id: self.editionID)
1114            let set: SetData = AllDay.getSetData(id: edition.setID)
1115            let name: String = set.name.concat(": #").concat(edition.playID.toString())
1116
1117            return MetadataViews.Edition(name: name, number: UInt64(self.serialNumber), max: edition.maxMintSize ?? nil)
1118        }
1119
1120        access(all) fun getTraits() : {String: AnyStruct} {
1121            let edition: EditionData = AllDay.getEditionData(id: self.editionID)
1122            let play: PlayData = AllDay.getPlayData(id: edition.playID)
1123            let series: SeriesData = AllDay.getSeriesData(id: edition.seriesID)
1124            let set: SetData = AllDay.getSetData(id: edition.setID)
1125
1126            let traitDictionary: {String: AnyStruct} = {
1127                "editionID": self.editionID,
1128                "editionTier": edition.tier,
1129                "parallel": edition.getParallel(),
1130                "seriesName": series.name,
1131                "setName": set.name,
1132                "serialNumber": self.serialNumber
1133            }
1134
1135            if let badges = self.getBadges(){
1136                traitDictionary.insert(key: "badges", badges)
1137            }
1138
1139            for name in play.metadata.keys {
1140                let value = play.metadata[name] ?? ""
1141                if value != "" {
1142                    traitDictionary.insert(key: name, value)
1143                }
1144            }
1145            return traitDictionary
1146        }
1147
1148        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
1149            return <- AllDay.createEmptyCollection(nftType: Type<@AllDay.NFT>())
1150        }
1151    }
1152
1153    //------------------------------------------------------------
1154    // Collection
1155    //------------------------------------------------------------
1156
1157    // A public collection interface that allows Moment NFTs to be borrowed
1158    //
1159    /// Deprecated: This is no longer used for defining access control anymore.
1160    access(all) resource interface MomentNFTCollectionPublic : NonFungibleToken.CollectionPublic {}
1161
1162    // An NFT Collection
1163    //
1164    access(all) resource Collection:
1165        NonFungibleToken.Collection,
1166        MomentNFTCollectionPublic
1167    {
1168        // dictionary of NFT conforming tokens
1169        // NFT is a resource type with an UInt64 ID field
1170        //
1171        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
1172
1173        // Return a list of NFT types that this receiver accepts
1174        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
1175            let supportedTypes: {Type: Bool} = {}
1176            supportedTypes[Type<@AllDay.NFT>()] = true
1177            return supportedTypes
1178        }
1179
1180        // Return whether or not the given type is accepted by the collection
1181        // A collection that can accept any type should just return true by default
1182        access(all) view fun isSupportedNFTType(type: Type): Bool {
1183            if type == Type<@AllDay.NFT>() {
1184                return true
1185            }
1186            return false
1187        }
1188
1189        // Return the amount of NFTs stored in the collection
1190        access(all) view fun getLength(): Int {
1191            return self.ownedNFTs.keys.length
1192        }
1193
1194        // Create an empty Collection for AllDay NFTs and return it to the caller
1195        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
1196            return <- AllDay.createEmptyCollection(nftType: Type<@AllDay.NFT>())
1197        }
1198
1199        // withdraw removes an NFT from the collection and moves it to the caller
1200        //
1201        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
1202            let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
1203
1204            emit Withdraw(id: token.id, from: self.owner?.address)
1205
1206            return <-token
1207        }
1208
1209        // deposit takes a NFT and adds it to the collections dictionary
1210        // and adds the ID to the id array
1211        //
1212        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
1213            let token <- token as! @AllDay.NFT
1214            let id: UInt64 = token.id
1215
1216            // add the new token to the dictionary which removes the old one
1217            let oldToken <- self.ownedNFTs[id] <- token
1218
1219            emit Deposit(id: id, to: self.owner?.address)
1220
1221            destroy oldToken
1222        }
1223
1224        // batchDeposit takes a Collection object as an argument
1225        // and deposits each contained NFT into this Collection
1226        //
1227        access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) {
1228            // Get an array of the IDs to be deposited
1229            let keys = tokens.getIDs()
1230
1231            // Iterate through the keys in the collection and deposit each one
1232            for key in keys {
1233                self.deposit(token: <-tokens.withdraw(withdrawID: key))
1234            }
1235
1236            // Destroy the empty Collection
1237            destroy tokens
1238        }
1239
1240        // getIDs returns an array of the IDs that are in the collection
1241        //
1242        access(all) view fun getIDs(): [UInt64] {
1243            return self.ownedNFTs.keys
1244        }
1245
1246        // borrowNFT gets a reference to an NFT in the collection
1247        //
1248        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
1249            return &self.ownedNFTs[id]
1250        }
1251
1252        // borrowMomentNFT gets a reference to an NFT in the collection
1253        //
1254        access(all) view fun borrowMomentNFT(id: UInt64): &AllDay.NFT? {
1255            return self.borrowNFT(id) as! &AllDay.NFT?
1256        }
1257
1258        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
1259            if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
1260                return nft as &{ViewResolver.Resolver}
1261            }
1262            return nil
1263        }
1264
1265        // Collection initializer
1266        //
1267        init() {
1268            self.ownedNFTs <- {}
1269        }
1270    }
1271
1272    // public function that anyone can call to create a new empty collection
1273    //
1274    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
1275        if nftType != Type<@AllDay.NFT>() {
1276            panic("NFT type is not supported")
1277        }
1278        return <- create Collection()
1279    }
1280
1281    //------------------------------------------------------------
1282    // Admin
1283    //------------------------------------------------------------
1284
1285    /// Entitlement that grants the ability to mint AllDay NFTs
1286    access(all) entitlement Mint
1287
1288    /// Entitlement that grants the ability to operate admin functions
1289    access(all) entitlement Operate
1290
1291    // This is no longer used for defining access control anymore.
1292    // Keeping this because removing it is not a valid change for contract update
1293    access(all) resource interface NFTMinter {}
1294
1295    // A resource that allows managing metadata and minting NFTs
1296    //
1297    access(all) resource Admin: NFTMinter {
1298        // Borrow a Series
1299        //
1300        access(self) view fun borrowSeries(id: UInt64): &AllDay.Series {
1301            pre {
1302                AllDay.seriesByID[id] != nil: "Cannot borrow series, no such id"
1303            }
1304
1305            return (&AllDay.seriesByID[id] as &AllDay.Series?)!
1306        }
1307
1308        // Borrow a Set
1309        //
1310        access(self) view fun borrowSet(id: UInt64): &AllDay.Set {
1311            pre {
1312                AllDay.setByID[id] != nil: "Cannot borrow Set, no such id"
1313            }
1314
1315            return (&AllDay.setByID[id] as &AllDay.Set?)!
1316        }
1317
1318        // Borrow a Play
1319        //
1320        access(self) view fun borrowPlay(id: UInt64): &AllDay.Play {
1321            pre {
1322                AllDay.playByID[id] != nil: "Cannot borrow Play, no such id"
1323            }
1324
1325            return (&AllDay.playByID[id] as &AllDay.Play?)!
1326        }
1327
1328        // Borrow an Edition
1329        //
1330        access(self) fun borrowEdition(id: UInt64): &AllDay.Edition {
1331            pre {
1332                AllDay.editionByID[id] != nil: "Cannot borrow edition, no such id"
1333            }
1334
1335            return (&AllDay.editionByID[id] as &AllDay.Edition?)!
1336        }
1337
1338        // Create a Series
1339        //
1340        access(Operate) fun createSeries(name: String): UInt64 {
1341            // Create and store the new series
1342            let series <- create AllDay.Series(
1343                name: name,
1344            )
1345            let seriesID = series.id
1346            AllDay.seriesByID[series.id] <-! series
1347
1348            // Return the new ID for convenience
1349            return seriesID
1350        }
1351
1352        // Close a Series
1353        //
1354        access(Operate) fun closeSeries(id: UInt64): UInt64 {
1355            if let series = &AllDay.seriesByID[id] as &AllDay.Series? {
1356                series.close()
1357                return series.id
1358            }
1359            panic("series does not exist")
1360        }
1361
1362        // Create a Set
1363        //
1364        access(Operate) fun createSet(name: String): UInt64 {
1365            // Create and store the new set
1366            let set <- create AllDay.Set(
1367                name: name,
1368            )
1369            let setID = set.id
1370            AllDay.setByID[set.id] <-! set
1371
1372            // Return the new ID for convenience
1373            return setID
1374        }
1375
1376        // Create a Play
1377        //
1378        access(Operate) fun createPlay(classification: String, metadata: {String: String}): UInt64 {
1379            // Create and store the new play
1380            let play <- create AllDay.Play(
1381                classification: classification,
1382                metadata: metadata,
1383            )
1384            let playID = play.id
1385            AllDay.playByID[play.id] <-! play
1386
1387            // Return the new ID for convenience
1388            return playID
1389        }
1390
1391        // Update a play's description metadata
1392        //
1393        access(Operate) fun updatePlayDescription(playID: UInt64, description: String): Bool {
1394            if let play = &AllDay.playByID[playID] as &AllDay.Play? {
1395                play.updateDescription(description: description)
1396            } else {
1397                panic("play does not exist")
1398            }
1399            return true
1400        }
1401
1402        // Update a dynamic moment/play's metadata
1403        //
1404        access(Operate) fun updateDynamicMetadata(playID: UInt64, optTeamName: String?, optPlayerFirstName: String?,
1405            optPlayerLastName: String?, optPlayerNumber: String?, optPlayerPosition: String?): Bool {
1406            if let play = &AllDay.playByID[playID] as &AllDay.Play? {
1407                play.updateDynamicMetadata(optTeamName: optTeamName, optPlayerFirstName: optPlayerFirstName,
1408                    optPlayerLastName: optPlayerLastName, optPlayerNumber: optPlayerNumber, optPlayerPosition: optPlayerPosition)
1409            } else {
1410                panic("play does not exist")
1411            }
1412            return true
1413        }
1414
1415        // Create an Edition
1416        //
1417         access(Operate) fun createEdition(
1418            seriesID: UInt64,
1419            setID: UInt64,
1420            playID: UInt64,
1421            maxMintSize: UInt64?,
1422            tier: String,
1423            parallel: String?
1424        ): UInt64 {
1425            let edition <- create Edition(
1426                seriesID: seriesID,
1427                setID: setID,
1428                playID: playID,
1429                maxMintSize: maxMintSize,
1430                tier: tier,
1431                parallel: parallel,
1432            )
1433            let editionID = edition.id
1434            AllDay.editionByID[edition.id] <-! edition
1435
1436            return editionID
1437        }
1438
1439        // Close an Edition
1440        //
1441        access(Operate) fun closeEdition(id: UInt64): UInt64 {
1442            if let edition = &AllDay.editionByID[id] as &AllDay.Edition? {
1443                edition.close()
1444                return edition.id
1445            }
1446            panic("edition does not exist")
1447        }
1448
1449        // Mint a single NFT
1450        // The Edition for the given ID must already exist
1451        //
1452        access(Mint) fun mintNFT(editionID: UInt64, serialNumber: UInt64?): @AllDay.NFT {
1453            pre {
1454                // Make sure the edition we are creating this NFT in exists
1455                AllDay.editionByID.containsKey(editionID): "No such EditionID"
1456            }
1457            return <- self.borrowEdition(id: editionID).mint(serialNumber: serialNumber)
1458        }
1459
1460        // Badge management functions
1461        access(Operate) fun createBadge(slug: String, title: String, description: String, visible: Bool, slugV2: String){
1462            var addOnsResource = AllDay.borrowAddOns()
1463            if addOnsResource == nil{
1464                AllDay.initializeAddOnsInStorage()
1465                addOnsResource = AllDay.borrowAddOns()
1466            }
1467            addOnsResource!.createBadge(slug: slug, title: title, description: description, visible: visible, slugV2: slugV2)
1468        }
1469
1470        access(Operate) fun updateBadge(slug: String, title: String?, description: String?, visible: Bool?, slugV2: String?, metadata: {String: String}?){
1471            AllDay.borrowAddOns()?.updateBadge(slug: slug, title: title, description: description, visible: visible, slugV2: slugV2, metadata: metadata)
1472        }
1473
1474        access(Operate) fun addBadgeToEntity(badgeSlug: String, entityType: BadgeEntityType, entityID: UInt64, metadata: {String: String}){
1475            AllDay.borrowAddOns()?.addBadgeToEntity(badgeSlug: badgeSlug, entityType: entityType, entityID: entityID, metadata: metadata)
1476        }
1477
1478        access(Operate) fun removeBadgeFromEntity(badgeSlug: String, entityType: BadgeEntityType, entityID: UInt64){
1479            AllDay.borrowAddOns()?.removeBadgeFromEntity(badgeSlug: badgeSlug, entityType: entityType, entityID: entityID)
1480        }
1481
1482        access(Operate) fun deleteBadge(slug: String){
1483            AllDay.borrowAddOns()?.deleteBadge(slug: slug)
1484        }
1485
1486    }
1487
1488        /// Return the metadata view types available for this contract
1489        ///
1490        access(all) view fun getContractViews(resourceType: Type?): [Type] {
1491            return [Type<MetadataViews.NFTCollectionData>(), Type<MetadataViews.NFTCollectionDisplay>(), Type<MetadataViews.Royalties>()]
1492        }
1493
1494        /// Resolve this contract's metadata views
1495        ///
1496        access(all) view fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
1497            post {
1498                result == nil || result!.getType() == viewType: "The returned view must be of the given type or nil"
1499            }
1500            switch viewType {
1501                case Type<MetadataViews.NFTCollectionData>():
1502                    return MetadataViews.NFTCollectionData(
1503                        storagePath: /storage/AllDayNFTCollection,
1504                        publicPath: /public/AllDayNFTCollection,
1505                        publicCollection: Type<&AllDay.Collection>(),
1506                        publicLinkedType: Type<&AllDay.Collection>(),
1507                        createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
1508                            return <-AllDay.createEmptyCollection(nftType: Type<@AllDay.NFT>())
1509                        })
1510                    )
1511                case Type<MetadataViews.NFTCollectionDisplay>():
1512                    let bannerImage = MetadataViews.Media(
1513                        file: MetadataViews.HTTPFile(
1514                            url: "https://assets.nflallday.com/flow/catalogue/NFLAD_BANNER.png"
1515                        ),
1516                        mediaType: "image/png"
1517                    )
1518                    let squareImage = MetadataViews.Media(
1519                        file: MetadataViews.HTTPFile(
1520                            url: "https://assets.nflallday.com/flow/catalogue/NFLAD_SQUARE.png"
1521                        ),
1522                        mediaType: "image/png"
1523                    )
1524                    return MetadataViews.NFTCollectionDisplay(
1525                        name: "NFL All Day",
1526                        description: "Officially Licensed Digital Collectibles Featuring the NFL’s Best Highlights. Buy, Sell and Collect Your Favorite NFL Moments",
1527                        externalURL: MetadataViews.ExternalURL("https://nflallday.com/"),
1528                        squareImage: squareImage,
1529                        bannerImage: bannerImage,
1530                        socials: {
1531                            "instagram": MetadataViews.ExternalURL("https://www.instagram.com/nflallday/"),
1532                            "twitter": MetadataViews.ExternalURL("https://twitter.com/NFLAllDay"),
1533                            "discord": MetadataViews.ExternalURL("https://discord.com/invite/5K6qyTzj2k")
1534                        }
1535                    )
1536                case Type<MetadataViews.Royalties>():
1537                    let royaltyReceiver: Capability<&{FungibleToken.Receiver}> =
1538                        getAccount(0xe4cf4bdc1751c65d).capabilities.get<&{FungibleToken.Receiver}>(MetadataViews.getRoyaltyReceiverPublicPath())!
1539                    return MetadataViews.Royalties(
1540                        [
1541                            MetadataViews.Royalty(
1542                                receiver: royaltyReceiver,
1543                                cut: 0.05,
1544                                description: "NFL All Day marketplace royalty"
1545                            )
1546                        ]
1547                    )
1548            }
1549            return nil
1550        }
1551
1552    //------------------------------------------------------------
1553    // Contract lifecycle
1554    //------------------------------------------------------------
1555
1556    // AllDay contract initializer
1557    //
1558    init() {
1559        // Set the named paths
1560        self.CollectionStoragePath = /storage/AllDayNFTCollection
1561        self.CollectionPublicPath = /public/AllDayNFTCollection
1562        self.AdminStoragePath = /storage/AllDayAdmin
1563        // Initialize the entity counts
1564        self.totalSupply = 0
1565        self.nextSeriesID = 1
1566        self.nextSetID = 1
1567        self.nextPlayID = 1
1568        self.nextEditionID = 1
1569
1570        // Initialize the metadata lookup dictionaries
1571        self.seriesByID <- {}
1572        self.seriesIDByName = {}
1573        self.setIDByName = {}
1574        self.setByID <- {}
1575        self.playByID <- {}
1576        self.editionByID <- {}
1577
1578        // Create an Admin resource and save it to storage
1579        let admin <- create Admin()
1580        self.account.storage.save(<-admin, to: self.AdminStoragePath)
1581
1582        //Initialize map to keep track of set+play+tier(edition) combinations that have been minted
1583        let setPlayTierMap: {String: Bool} = {}
1584        self.account.storage.save(setPlayTierMap, to: AllDay.getSetPlayTierMapStorage())
1585
1586        // Let the world know we are here
1587        emit ContractInitialized()
1588    }
1589}
1590