Smart Contract
AllDay
A.e4cf4bdc1751c65d.AllDay
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