Smart Contract

Crowdflix

A.ad9300d3ca41f63a.Crowdflix

Valid From

125,008,172

Deployed

1w ago
Feb 16, 2026, 11:36:26 AM UTC

Dependents

12 imports
1/*
2    Description: Central Smart Contract for Crowdflix NFT Platform
3
4    This smart contract defines the primary logic for the Crowdflix platform — 
5    a Flow-based NFT marketplace for collectible cinematic moments from movies, 
6    series and more.
7
8    The contract manages metadata and minting rights for MovieScenes and Sets,
9    which act as templates for minting collectible Moments (NFTs).
10
11    When a new MovieScene wants to be added to the records, an Admin creates
12    a new MovieScene struct that is stored in the smart contract.
13
14    Then an Admin can create new Sets. Sets consist of a public struct that
15    contains public information about a set, and a private resource used
16    to mint new moments based off of movieScenes that have been linked to the Set.
17
18    The admin resource has the power to do all of the important actions
19    in the smart contract. When admins want to call functions in a set,
20    they call their borrowSet function to get a reference
21    to a set in the contract. Then, they can call functions on the set using that reference.
22
23    In this way, the smart contract and its defined resources interact
24    with great teamwork.
25
26    When moments are minted, they are initialized with a MomentData struct and
27    are returned by the minter.
28
29    The contract also defines a Collection resource. This is an object that
30    every Crowdflix NFT owner will store in their account
31    to manage their NFT collection.
32
33    The main Crowdflix account will also have its own Moment collections
34    it can use to hold its own moments that have not yet been sent to a user.
35
36    Note: All state changing functions will panic if an invalid argument is
37    provided or one of its pre-conditions or post conditions aren't met.
38    Functions that don't modify state will simply return 0 or nil
39    and those cases need to be handled by the caller.
40
41*/
42
43// Testnet
44// import FungibleToken from 0x9a0766d93b6608b7
45// import NonFungibleToken from 0x631e88ae7f1d7c20
46// import MetadataViews from 0x631e88ae7f1d7c20
47// import ViewResolver from 0x631e88ae7f1d7c20
48// Production
49import FungibleToken from 0xf233dcee88fe0abe
50import NonFungibleToken from 0x1d7e57aa55817448
51import MetadataViews from 0x1d7e57aa55817448
52import ViewResolver from 0x1d7e57aa55817448
53
54access(all) contract Crowdflix: NonFungibleToken {
55    // -----------------------------------------------------------------------
56    // Crowdflix deployment variables
57    // -----------------------------------------------------------------------
58
59    // The network the contract is deployed on
60    access(all) view fun Network(): String { return "production" }
61
62    // The address to which royalties should be deposited
63    access(all) view fun RoyaltyAddress(): Address { return 0x474b92dbf17fda56 }
64
65    // The path to the Subedition Admin resource belonging to the Account
66    // which the contract is deployed on
67    access(all) view fun SubeditionAdminStoragePath(): StoragePath { return /storage/CrowdflixSubeditionAdmin}
68
69    // -----------------------------------------------------------------------
70    // Crowdflix contract Events
71    // -----------------------------------------------------------------------
72
73    // Emitted when a new MovieScene struct is created
74    access(all) event MovieSceneCreated(id: UInt32, metadata: {String: String})
75    // Emitted when a new series has been triggered by an admin
76    access(all) event NewSeriesStarted(newCurrentSeries: UInt32)
77
78    // Events for Set-Related actions
79    //
80    // Emitted when a new Set is created
81    access(all) event SetCreated(setID: UInt32, series: UInt32)
82    // Emitted when a new MovieScene is added to a Set
83    access(all) event MovieSceneAddedToSet(setID: UInt32, movieSceneID: UInt32)
84    // Emitted when a MovieScene is retired from a Set and cannot be used to mint
85    access(all) event MovieSceneRetiredFromSet(setID: UInt32, movieSceneID: UInt32, numMoments: UInt32)
86    // Emitted when a Set is locked, meaning MovieScenes cannot be added
87    access(all) event SetLocked(setID: UInt32)
88    // Emitted when a Moment is minted from a Set
89    access(all) event MomentMinted(momentID: UInt64, movieSceneID: UInt32, setID: UInt32, serialNumber: UInt32, subeditionID: UInt32)
90
91    // Events for Collection-related actions
92    //
93    // Emitted when a moment is withdrawn from a Collection
94    access(all) event Withdraw(id: UInt64, from: Address?)
95    // Emitted when a moment is deposited into a Collection
96    access(all) event Deposit(id: UInt64, to: Address?)
97
98    // Emitted when a Moment is destroyed
99    access(all) event MomentDestroyed(id: UInt64)
100
101    // Emitted when a Subedition is created
102    access(all) event SubeditionCreated(subeditionID: UInt32, name: String, metadata: {String: String})
103
104    // Emitted when a Subedition is linked to the specific Moment
105    access(all) event SubeditionAddedToMoment(momentID: UInt64, subeditionID: UInt32, setID: UInt32, movieSceneID: UInt32)
106
107    // -----------------------------------------------------------------------
108    // Crowdflix contract-level fields.
109    // These contain actual values that are stored in the smart contract.
110    // -----------------------------------------------------------------------
111
112    // Series that this Set belongs to.
113    // Series is a concept that indicates a group of Sets through time.
114    // Many Sets can exist at a time, but only one series.
115    access(all) var currentSeries: UInt32
116
117    // Variable size dictionary of MovieScene structs
118    access(self) var movieSceneDatas: {UInt32: MovieScene}
119
120    // Variable size dictionary of SetData structs
121    access(self) var setDatas: {UInt32: SetData}
122
123    // Variable size dictionary of Set resources
124    access(self) var sets: @{UInt32: Set}
125
126    // The ID that is used to create MovieScenes.
127    // Every time a MovieScene is created, movieSceneID is assigned
128    // to the new MovieScene's ID and then is incremented by 1.
129    access(all) var nextMovieSceneID: UInt32
130
131    // The ID that is used to create Sets. Every time a Set is created
132    // setID is assigned to the new set's ID and then is incremented by 1.
133    access(all) var nextSetID: UInt32
134
135    // The total number of Crowdflix NFTs that have been created
136    // Because NFTs can be destroyed, it doesn't necessarily mean that this
137    // reflects the total number of NFTs in existence, just the number that
138    // have been minted to date. Also used as global moment IDs for minting.
139    access(all) var totalSupply: UInt64
140
141    // -----------------------------------------------------------------------
142    // Crowdflix contract-level Composite Type definitions
143    // -----------------------------------------------------------------------
144    // These are just *definitions* for Types that this contract
145    // and other accounts can use. These definitions do not contain
146    // actual stored values, but an instance (or object) of one of these Types
147    // can be created by this contract that contains stored values.
148    // -----------------------------------------------------------------------
149
150    // MovieScene is a Struct that holds metadata associated
151    // with a specific movie scene, like the legendary scene when
152    // Iron Man snapped his fingers in Avengers Endgame
153    // or when Neo chose the red pill in The Matrix.
154    //
155    // Moment NFTs will all reference a single MovieScene as the owner of
156    // its metadata. The MovieScene are publicly accessible, so anyone can
157    // read the metadata associated with a specific MovieScene ID
158    //
159    access(all) struct MovieScene {
160        // The unique ID for the MovieScene
161        access(all) let movieSceneID: UInt32
162
163        // Stores all the metadata about the MovieScene as a string mapping
164        // This is not the long term way NFT metadata will be stored. It's a temporary
165        // construct while we figure out a better way to do metadata.
166        //
167        access(all) let metadata: {String: String}
168
169        init(metadata: {String: String}) {
170            pre {
171                metadata.length != 0: "New MovieScene metadata cannot be empty"
172            }
173            self.movieSceneID = Crowdflix.nextMovieSceneID
174            self.metadata = metadata
175        }
176    }
177
178    // A Set is a grouping of MovieScenes that have occured in the real world
179    // that make up a related group of collectibles, like sets of baseball
180    // or Magic cards. A MovieScene can exist in multiple different sets.
181    //
182    // SetData is a struct that is stored in a field of the contract.
183    // Anyone can query the constant information
184    // about a set by calling various getters located
185    // at the end of the contract. Only the admin has the ability
186    // to modify any data in the private Set resource.
187    //
188    access(all) struct SetData {
189        // Unique ID for the Set
190        access(all) let setID: UInt32
191
192        // Name of the Set
193        // ex. "Times when Marvel heroes made terrible decisions"
194        access(all) let name: String
195
196        // Series that this Set belongs to.
197        // Series is a concept that indicates a group of Sets through time.
198        // Many Sets can exist at a time, but only one series.
199        access(all) let series: UInt32
200
201        init(name: String) {
202            pre {
203                name.length > 0: "New Set name cannot be empty"
204            }
205            self.setID = Crowdflix.nextSetID
206            self.name = name
207            self.series = Crowdflix.currentSeries
208        }
209    }
210
211    // Set is a resource type that contains the functions to add and remove
212    // MovieScenes from a set and mint Moments.
213    //
214    // It is stored in a private field in the contract so that
215    // the admin resource can call its methods.
216    //
217    // The admin can add MovieScenes to a Set so that the set can mint Moments
218    // that reference that playdata.
219    // The Moments that are minted by a Set will be listed as belonging to
220    // the Set that minted it, as well as the MovieScenes it references.
221    //
222    // Admin can also retire MovieScenes from the Set, meaning that the retired
223    // MovieScene can no longer have Moments minted from it.
224    //
225    // If the admin locks the Set, no more MovieScenes can be added to it, but
226    // Moments can still be minted.
227    //
228    // If retireAll() and lock() are called back-to-back,
229    // the Set is closed off forever and nothing more can be done with it.
230    access(all) resource Set {
231        // Unique ID for the set
232        access(all) let setID: UInt32
233
234        // Array of MovieScenes that are a part of this set.
235        // When a MovieScene is added to the set, its ID gets appended here.
236        // The ID does not get removed from this array when a MovieScene is retired.
237        access(contract) var movieScenes: [UInt32]
238
239        // Map of MovieScene IDs that Indicates if a MovieScene in this Set can be minted.
240        // When a MovieScene is added to a Set, it is mapped to false (not retired).
241        // When a MovieScene is retired, this is set to true and cannot be changed.
242        access(contract) var retired: {UInt32: Bool}
243
244        // Indicates if the Set is currently locked.
245        // When a Set is created, it is unlocked
246        // and MovieScenes are allowed to be added to it.
247        // When a set is locked, MovieScenes cannot be added.
248        // A Set can never be changed from locked to unlocked,
249        // the decision to lock a Set it is final.
250        // If a Set is locked, MovieScenes cannot be added, but
251        // Moments can still be minted from MovieScenes
252        // that exist in the Set.
253        access(all) var locked: Bool
254
255        // Mapping of MovieScene IDs that indicates the number of Moments
256        // that have been minted for specific MovieScenes in this Set.
257        // When a Moment is minted, this value is stored in the Moment to
258        // show its place in the Set, eg. 13 of 60.
259        access(contract) var numberMintedPerMovieScene: {UInt32: UInt32}
260
261        init(name: String) {
262            self.setID = Crowdflix.nextSetID
263            self.movieScenes = []
264            self.retired = {}
265            self.locked = false
266            self.numberMintedPerMovieScene = {}
267
268            // Create a new SetData for this Set and store it in contract storage
269            Crowdflix.setDatas[self.setID] = SetData(name: name)
270        }
271
272        // addMovieScene adds a MovieScene to the set
273        //
274        // Parameters: movieSceneID: The ID of the MovieScene that is being added
275        //
276        // Pre-Conditions:
277        // The MovieScene needs to be an existing
278        // The Set needs to be not locked
279        // The MovieScene can't have already been added to the Set
280        //
281        access(all) fun addMovieScene(movieSceneID: UInt32) {
282            pre {
283                Crowdflix.movieSceneDatas[movieSceneID] != nil: "Cannot add the MovieScene to Set: MovieScene doesn't exist."
284                !self.locked: "Cannot add the MovieScene to the Set after the set has been locked."
285                self.numberMintedPerMovieScene[movieSceneID] == nil: "The MovieScene has already beed added to the set."
286            }
287
288            // Add the MovieScene to the array of Plays
289            self.movieScenes.append(movieSceneID)
290
291            // Open the MovieScene up for minting
292            self.retired[movieSceneID] = false
293
294            // Initialize the Moment count to zero
295            self.numberMintedPerMovieScene[movieSceneID] = 0
296
297            emit MovieSceneAddedToSet(setID: self.setID, movieSceneID: movieSceneID)
298        }
299
300        // addMovieScenes adds multiple MovieScenes to the Set
301        //
302        // Parameters: movieSceneIDs: The IDs of the MovieScenes that are being added
303        //                      as an array
304        //
305        access(all) fun addMovieScenes(movieSceneIDs: [UInt32]) {
306            for movieScene in movieSceneIDs {
307                self.addMovieScene(movieSceneID: movieScene)
308            }
309        }
310
311        // retireMovieScene retires a MovieScene from the Set so that it can't mint new Moments
312        //
313        // Parameters: movieSceneID: The ID of the MovieScene that is being retired
314        //
315        // Pre-Conditions:
316        // The MovieScene is part of the Set and not retired (available for minting).
317        //
318        access(all) fun retireMovieScene(movieSceneID: UInt32) {
319            pre {
320                self.retired[movieSceneID] != nil: "Cannot retire the MovieScene: MovieScene doesn't exist in this set!"
321            }
322
323            if !self.retired[movieSceneID]! {
324                self.retired[movieSceneID] = true
325
326                emit MovieSceneRetiredFromSet(setID: self.setID, movieSceneID: movieSceneID, numMoments: self.numberMintedPerMovieScene[movieSceneID]!)
327            }
328        }
329
330        // retireAll retires all the MovieScenes in the Set
331        // Afterwards, none of the retired MovieScenes will be able to mint new Moments
332        //
333        access(all) fun retireAll() {
334            for movieScene in self.movieScenes {
335                self.retireMovieScene(movieSceneID: movieScene)
336            }
337        }
338
339        // lock() locks the Set so that no more MovieScenes can be added to it
340        //
341        // Pre-Conditions:
342        // The Set should not be locked
343        access(all) fun lock() {
344            if !self.locked {
345                self.locked = true
346                emit SetLocked(setID: self.setID)
347            }
348        }
349
350        // mintMoment mints a new Moment and returns the newly minted Moment
351        //
352        // Parameters: movieSceneID: The ID of the MovieScene that the Moment references
353        //
354        // Pre-Conditions:
355        // The MovieScene must exist in the Set and be allowed to mint new Moments
356        //
357        // Returns: The NFT that was minted
358        //
359        access(all) fun mintMoment(movieSceneID: UInt32): @NFT {
360            pre {
361                self.retired[movieSceneID] != nil: "Cannot mint the moment: This MovieScene doesn't exist."
362                !self.retired[movieSceneID]!: "Cannot mint the moment from this MovieScene: This MovieScene has been retired."
363            }
364
365            // Gets the number of Moments that have been minted for this MovieScene
366            // to use as this Moment's serial number
367            let numInMovieScene = self.numberMintedPerMovieScene[movieSceneID]!
368
369            // Mint the new moment
370            let newMoment: @NFT <- create NFT(
371                serialNumber: numInMovieScene + UInt32(1),
372                movieSceneID: movieSceneID,
373                setID: self.setID,
374                subeditionID: 0
375            )
376
377            // Increment the count of Moments minted for this MovieScene
378            self.numberMintedPerMovieScene[movieSceneID] = numInMovieScene + UInt32(1)
379
380            return <-newMoment
381        }
382
383        // batchMintMoment mints an arbitrary quantity of Moments
384        // and returns them as a Collection
385        //
386        // Parameters: movieSceneID: the ID of the MovieScene that the Moments are minted for
387        //             quantity: The quantity of Moments to be minted
388        //
389        // Returns: Collection object that contains all the Moments that were minted
390        //
391        access(all) fun batchMintMoment(movieSceneID: UInt32, quantity: UInt64): @Collection {
392            let newCollection <- create Collection()
393
394            var i: UInt64 = 0
395            while i < quantity {
396                newCollection.deposit(token: <-self.mintMoment(movieSceneID: movieSceneID))
397                i = i + UInt64(1)
398            }
399
400            return <- newCollection
401        }
402
403        // mintMomentWithSubedition mints a new Moment with subedition and returns the newly minted Moment
404        //
405        // Parameters: movieSceneID: The ID of the MovieScene that the Moment references
406        //             subeditionID: The ID of the subedition within Edition that the Moment references
407        //
408        // Pre-Conditions:
409        // The MovieScene must exist in the Set and be allowed to mint new Moments
410        //
411        // Returns: The NFT that was minted
412        //
413        access(all) fun mintMomentWithSubedition(movieSceneID: UInt32, subeditionID: UInt32): @NFT {
414            pre {
415                self.retired[movieSceneID] != nil: "Cannot mint the moment: This MovieScene doesn't exist."
416                !self.retired[movieSceneID]!: "Cannot mint the moment from this MovieScene: This MovieScene has been retired."
417            }
418
419            // Gets the number of Moments that have been minted for this subedition
420            // to use as this Moment's serial number
421            let subeditionRef = Crowdflix.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
422                ?? panic("No subedition admin resource in storage")
423
424            let numInSubedition = subeditionRef.getNumberMintedPerSubedition(
425                setID: self.setID,
426                movieSceneID: movieSceneID,
427                subeditionID: subeditionID
428            )
429
430            // Mint the new moment
431            let newMoment: @NFT <- create NFT(
432                serialNumber: numInSubedition + UInt32(1),
433                movieSceneID: movieSceneID,
434                setID: self.setID,
435                subeditionID: subeditionID
436            )
437
438            // Increment the count of Moments minted for this subedition
439            subeditionRef.addToNumberMintedPerSubedition(
440                setID: self.setID,
441                movieSceneID: movieSceneID,
442                subeditionID: subeditionID
443            )
444
445            subeditionRef.setMomentsSubedition(nftID: newMoment.id, subeditionID: subeditionID, setID: self.setID, movieSceneID: movieSceneID)
446
447            self.numberMintedPerMovieScene[movieSceneID] = self.numberMintedPerMovieScene[movieSceneID]! + UInt32(1)
448
449            return <- newMoment
450        }
451
452        // batchMintMomentWithSubedition mints an arbitrary quantity of Moments with subedition
453        // and returns them as a Collection
454        //
455        // Parameters: movieSceneID: the ID of the MovieScene that the Moments are minted for
456        //             quantity: The quantity of Moments to be minted
457        //             subeditionID: The ID of the subedition within Edition that the Moments references
458        //
459        // Returns: Collection object that contains all the Moments that were minted
460        //
461        access(all) fun batchMintMomentWithSubedition(movieSceneID: UInt32, quantity: UInt64, subeditionID: UInt32): @Collection {
462            let newCollection <- create Collection()
463
464            var i: UInt64 = 0
465            while i < quantity {
466                newCollection.deposit(token: <-self.mintMomentWithSubedition(movieSceneID: movieSceneID, subeditionID: subeditionID))
467                i = i + UInt64(1)
468            }
469
470            return <-newCollection
471        }
472
473        access(all) view fun getMovieScenes(): [UInt32] {
474            return self.movieScenes
475        }
476
477        access(all) view fun getRetired(): {UInt32: Bool} {
478            return self.retired
479        }
480
481        access(all) view fun getNumberMintedPerMovieScene(): {UInt32: UInt32} {
482            return self.numberMintedPerMovieScene
483        }
484    }
485
486    // Struct that contains all of the important data about a set
487    // Can be easily queried by instantiating the `QuerySetData` object
488    // with the desired set ID
489    // let setData = Crowdflix.QuerySetData(setID: 12)
490    //
491    access(all) struct QuerySetData {
492        access(all) let setID: UInt32
493        access(all) let name: String
494        access(all) let series: UInt32
495        access(self) var movieScenes: [UInt32]
496        access(self) var retired: {UInt32: Bool}
497        access(all) var locked: Bool
498        access(self) var numberMintedPerMovieScene: {UInt32: UInt32}
499
500        init(setID: UInt32) {
501            pre {
502                Crowdflix.sets[setID] != nil: "The set with the provided ID does not exist"
503            }
504
505            let set = (&Crowdflix.sets[setID] as &Set?)!
506            let setData = Crowdflix.setDatas[setID]!
507
508            self.setID = setID
509            self.name = setData.name
510            self.series = setData.series
511            self.movieScenes = set.getMovieScenes()
512            self.retired = set.getRetired()
513            self.locked = set.locked
514            self.numberMintedPerMovieScene = set.getNumberMintedPerMovieScene()
515        }
516
517        access(all) view fun getMovieScenes(): [UInt32] {
518            return self.movieScenes
519        }
520
521        access(all) view fun getRetired(): {UInt32: Bool} {
522            return self.retired
523        }
524
525        access(all) view fun getNumberMintedPerMovieScene(): {UInt32: UInt32} {
526            return self.numberMintedPerMovieScene
527        }
528    }
529
530    access(all) struct MomentData {
531        // The ID of the Set that the Moment comes from
532        access(all) let setID: UInt32
533
534        // The ID of the MovieScene that the Moment references
535        access(all) let movieSceneID: UInt32
536
537        // The place in the edition that this Moment was minted
538        // Otherwise know as the serial number
539        access(all) let serialNumber: UInt32
540
541        init(setID: UInt32, movieSceneID: UInt32, serialNumber: UInt32) {
542            self.setID = setID
543            self.movieSceneID = movieSceneID
544            self.serialNumber = serialNumber
545        }
546    }
547
548    // This is an implementation of a custom metadata view for MovieSceneMoment.
549    // This view contains the MovieScene metadata.
550    //
551    access(all) struct MovieSceneMomentMetadataView {
552        access(all) let title: String?
553        access(all) let sceneDescription: String?
554        access(all) let universe: String?
555        access(all) let studio: String?
556        access(all) let contentType: String? 
557        access(all) let characterNames: String?
558        access(all) let actorNames: String?
559        access(all) let directorOrCreator: String?
560        access(all) let releaseYear: String?
561        access(all) let genre: String?
562        access(all) let dropDate: String?
563        access(all) let dateOfMoment: String?
564        access(all) let sceneCategory: String?
565        access(all) let sceneType: String?
566        access(all) let seriesNumber: UInt32?
567        access(all) let setName: String?
568        access(all) let serialNumber: UInt32
569        access(all) let movieSceneID: UInt32
570        access(all) let setID: UInt32
571        access(all) let numMomentsInEdition: UInt32?
572
573        init(
574            title: String?,
575            sceneDescription: String?,
576            universe: String?,
577            studio: String?,
578            contentType: String?,
579            characterNames: String?,  
580            actorNames: String?,
581            directorOrCreator: String?,
582            releaseYear: String?,
583            genre: String?,
584            dropDate: String?,
585            dateOfMoment: String?,
586            sceneCategory: String?,
587            sceneType: String?,
588            seriesNumber: UInt32?,
589            setName: String?,
590            serialNumber: UInt32,
591            movieSceneID: UInt32,
592            setID: UInt32,
593            numMomentsInEdition: UInt32?
594        ) {
595            self.title = title
596            self.sceneDescription = sceneDescription
597            self.universe = universe
598            self.studio = studio
599            self.contentType = contentType
600            self.characterNames = characterNames
601            self.actorNames = actorNames
602            self.directorOrCreator = directorOrCreator
603            self.releaseYear = releaseYear
604            self.genre = genre
605            self.dropDate = dropDate
606            self.dateOfMoment = dateOfMoment
607            self.sceneCategory = sceneCategory
608            self.sceneType = sceneType
609            self.seriesNumber = seriesNumber
610            self.setName = setName
611            self.serialNumber = serialNumber
612            self.movieSceneID = movieSceneID
613            self.setID = setID
614            self.numMomentsInEdition = numMomentsInEdition
615        }
616    }
617
618    // The resource that represents the Moment NFTs
619    //
620    access(all) resource NFT: NonFungibleToken.NFT {
621        // Global unique moment ID
622        access(all) let id: UInt64
623
624        // Struct of Moment metadata
625        access(all) let data: MomentData
626
627        init(serialNumber: UInt32, movieSceneID: UInt32, setID: UInt32, subeditionID: UInt32) {
628            // Increment the global Moment IDs
629            Crowdflix.totalSupply = Crowdflix.totalSupply + UInt64(1)
630
631            self.id = Crowdflix.totalSupply
632
633            // Set the metadata struct
634            self.data = MomentData(setID: setID, movieSceneID: movieSceneID, serialNumber: serialNumber)
635
636            emit MomentMinted(
637                momentID: self.id,
638                movieSceneID: movieSceneID,
639                setID: self.data.setID,
640                serialNumber: self.data.serialNumber,
641                subeditionID: subeditionID
642            )
643        }
644
645        // If the Moment is destroyed, emit an event to indicate
646        // to outside observers that it has been destroyed
647        access(all) event ResourceDestroyed(
648            id: UInt64 = self.id,
649            serialNumber: UInt32 =  self.data.serialNumber,
650            movieSceneID: UInt32 =  self.data.movieSceneID,
651            setID: UInt32 = self.data.setID
652        )
653
654        access(all) view fun name(): String {
655            let title: String = Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "Title") ?? ""
656            let sceneType: String = Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "SceneType") ?? ""
657            return title
658                .concat(" ")
659                .concat(sceneType)
660        }
661
662        // The description of the Moment.
663        // build the description using set, series, and serial number.
664        access(all) view fun description(): String {
665
666            // Build the description using set name, series number, and serial number
667            let setName: String = Crowdflix.getSetName(setID: self.data.setID) ?? ""
668            let serialNumber: String = self.data.serialNumber.toString()
669            let seriesNumber: String = Crowdflix.getSetSeries(setID: self.data.setID)?.toString() ?? ""
670            return "A series "
671                .concat(seriesNumber)
672                .concat(" ")
673                .concat(setName)
674                .concat(" moment with serial number ")
675                .concat(serialNumber)
676        }
677
678        // All supported metadata views for the Moment including the Core NFT Views
679        access(all) view fun getViews(): [Type] {
680            return [
681                Type<MetadataViews.Display>(),
682                Type<MovieSceneMomentMetadataView>(),
683                Type<MetadataViews.Royalties>(),
684                Type<MetadataViews.Editions>(),
685                Type<MetadataViews.ExternalURL>(),
686                Type<MetadataViews.NFTCollectionData>(),
687                Type<MetadataViews.NFTCollectionDisplay>(),
688                Type<MetadataViews.Serial>(),
689                Type<MetadataViews.Traits>(),
690                Type<MetadataViews.Medias>()
691            ]
692        }
693
694        // resolves the view with the given type for the NFT
695        access(all) fun resolveView(_ view: Type): AnyStruct? {
696            switch view {
697                case Type<MetadataViews.Display>():
698                    return MetadataViews.Display(
699                        name: self.name(),
700                        description: self.description(),
701                        thumbnail: MetadataViews.HTTPFile(url: self.posterimage())
702                    )
703                // Custom metadata view unique to Crowdflix Moments
704                case Type<MovieSceneMomentMetadataView>():
705                    return MovieSceneMomentMetadataView(
706                        title: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "Title"),
707                        sceneDescription: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "SceneDescription"),
708                        universe: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "Universe"),
709                        studio: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "Studio"),
710                        contentType: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "ContentType"),
711                        characterNames: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "CharacterNames"),
712                        actorNames: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "ActorNames"),
713                        directorOrCreator: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "DirectorOrCreator"),
714                        releaseYear: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "ReleaseYear"),
715                        genre: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "Genre"),
716                        dropDate: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "DropDate"),
717                        dateOfMoment: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "DateOfMoment"),
718                        sceneCategory: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "SceneCategory"),
719                        sceneType: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "SceneType"),
720                        seriesNumber: Crowdflix.getSetSeries(setID: self.data.setID),
721                        setName: Crowdflix.getSetName(setID: self.data.setID),
722                        serialNumber: self.data.serialNumber,
723                        movieSceneID: self.data.movieSceneID,
724                        setID: self.data.setID,
725                        numMomentsInEdition: Crowdflix.getNumMomentsInEdition(setID: self.data.setID, movieSceneID: self.data.movieSceneID)
726                    )
727                case Type<MetadataViews.Editions>():
728                    let name = self.getEditionName()
729                    let max = Crowdflix.getNumMomentsInEdition(setID: self.data.setID, movieSceneID: self.data.movieSceneID) ?? 0
730                    let editionInfo = MetadataViews.Edition(name: name, number: UInt64(self.data.serialNumber), max: max > 0 ? UInt64(max) : nil)
731                    let editionList: [MetadataViews.Edition] = [editionInfo]
732                    return MetadataViews.Editions(
733                        editionList
734                    )
735                case Type<MetadataViews.Serial>():
736                    return MetadataViews.Serial(
737                        UInt64(self.data.serialNumber)
738                    )
739                case Type<MetadataViews.Royalties>():
740                    return Crowdflix.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.Royalties>())
741                case Type<MetadataViews.ExternalURL>():
742                    return MetadataViews.ExternalURL(self.getMomentURL())
743                case Type<MetadataViews.NFTCollectionData>():
744                    return Crowdflix.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionData>())
745                case Type<MetadataViews.NFTCollectionDisplay>():
746                    return Crowdflix.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionDisplay>())
747                case Type<MetadataViews.Traits>():
748                    return self.resolveTraitsView()
749                case Type<MetadataViews.Medias>():
750                    return MetadataViews.Medias(
751                        [
752                            MetadataViews.Media(
753                                file: MetadataViews.HTTPFile(
754                                    url: self.posterimage()
755                                ),
756                                mediaType: "image/jpeg"
757                            ),
758                            MetadataViews.Media(
759                                file: MetadataViews.HTTPFile(
760                                    url: self.video()
761                                ),
762                                mediaType: "video/mp4"
763                            )
764                        ]
765                    )
766            }
767            return nil
768        }
769
770        // resolves this NFT's Traits view
771        access(all) fun resolveTraitsView(): MetadataViews.Traits {
772            // sports radar team id
773            let excludedNames: [String] = []
774
775            // Get subedition
776            let subedition = Crowdflix.getSubeditionByNFTID(self.id)
777
778            // Create a dictionary of this NFT's traits with default metadata
779            var traits: {String: AnyStruct} = {
780                "SeriesNumber": Crowdflix.getSetSeries(setID: self.data.setID),
781                "SetName": Crowdflix.getSetName(setID: self.data.setID),
782                "SerialNumber": self.data.serialNumber,
783                "Subedition": subedition?.name ?? "Standard",
784                "SubeditionID": subedition?.subeditionID ?? 0
785            }
786
787            // Add MovieScene specific data
788            traits = self.mapMovieSceneData(dict: traits)
789
790            return MetadataViews.dictToTraits(dict: traits, excludedNames: excludedNames)
791        }
792
793        // Functions used for computing MetadataViews
794
795        // mapMovieSceneData helps build our trait map from MovieScene metadata
796        // Returns: The trait map with all non-empty fields from MovieScene data added
797        access(all) fun mapMovieSceneData(dict: {String: AnyStruct}) : {String: AnyStruct} {
798            let movieSceneMetadata = Crowdflix.getMovieSceneMetaData(movieSceneID: self.data.movieSceneID) ?? {}
799            for name in movieSceneMetadata.keys {
800                let value = movieSceneMetadata[name] ?? ""
801                if value != "" {
802                    dict.insert(key: name, value)
803                }
804            }
805            return dict
806        }
807
808        // getMomentURL
809        // Returns: The computed external url of the moment
810        access(all) view fun getMomentURL(): String {
811            return "https://marketplace.crowdflix.io/moment/".concat(self.id.toString())
812        }
813
814        // getEditionName Moment's edition name is a combination of the Moment's setName and movieSceneID
815        // `setName: #movieSceneID`
816        access(all) view fun getEditionName(): String {
817            let setName: String = Crowdflix.getSetName(setID: self.data.setID) ?? ""
818            let editionName = setName.concat(": #").concat(self.data.movieSceneID.toString())
819            return editionName
820        }
821
822        access(all) view fun assetPath(): String {
823            return "https://assets.crowdflix.io/media/".concat(self.id.toString())
824        }
825
826        // returns a url to display poster image
827        access(all) view fun posterimage(): String {
828            return self.appendOptionalParams(url: self.assetPath().concat("/poster"), firstDelim: "?")
829        }
830
831        // a url to display a video associated with the moment
832        access(all) view fun video(): String {
833            return self.appendOptionalParams(url: self.assetPath().concat("/video"), firstDelim: "?")
834        }
835
836        // appends and optional network param needed to resolve the media
837        access(all) view fun appendOptionalParams(url: String, firstDelim: String): String {
838            if Crowdflix.Network() == "testnet" {
839                return url.concat(firstDelim).concat("testnet")
840            }
841            return url
842        }
843
844        // Create an empty Collection for Crowdflix NFTs and return it to the caller
845        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
846            return <- Crowdflix.createEmptyCollection(nftType: Type<@NFT>())
847        }
848    }
849
850    // Admin is a special authorization resource that
851    // allows the owner to perform important functions to modify the
852    // various aspects of the Plays, Sets, and Moments
853    //
854    access(all) resource Admin {
855        // createMovieScene creates a new MovieScene struct
856        // and stores it in the MovieScenes dictionary in the Crowdflix smart contract
857        //
858        // Parameters:  metadata: A dictionary mapping metadata titles to their data
859        //                       example: {"Title": "Avengers: Endgame", "sceneDescription": "Iron Man's iconic snap defeating Thanos"}
860        //
861        // Returns: the ID of the new MovieScene object
862        //
863        access(all) fun createMovieScene(metadata: {String: String}): UInt32 {
864            // Create the new MovieScene
865            var newMovieScene = MovieScene(metadata: metadata)
866            let newID = newMovieScene.movieSceneID
867
868            // Increment the ID so that it isn't used again
869            Crowdflix.nextMovieSceneID = Crowdflix.nextMovieSceneID + UInt32(1)
870
871            emit MovieSceneCreated(id: newMovieScene.movieSceneID, metadata: metadata)
872
873            // Store it in the contract storage
874            Crowdflix.movieSceneDatas[newID] = newMovieScene
875
876            return newID
877        }
878
879        // createSet creates a new Set resource and stores it
880        // in the sets mapping in the Crowdflix contract
881        //
882        // Parameters: name: The name of the Set
883        //
884        // Returns: The ID of the created set
885        access(all) fun createSet(name: String): UInt32 {
886            // Create the new Set
887            var newSet <- create Set(name: name)
888
889            // Increment the setID so that it isn't used again
890            Crowdflix.nextSetID = Crowdflix.nextSetID + UInt32(1)
891
892            let newID = newSet.setID
893
894            emit SetCreated(setID: newSet.setID, series: Crowdflix.currentSeries)
895
896            // Store it in the sets mapping field
897            Crowdflix.sets[newID] <-! newSet
898
899            return newID
900        }
901
902        // borrowSet returns a reference to a set in the Crowdflix
903        // contract so that the admin can call methods on it
904        //
905        // Parameters: setID: The ID of the Set that you want to
906        // get a reference to
907        //
908        // Returns: A reference to the Set with all of the fields
909        // and methods exposed
910        //
911        access(all) view fun borrowSet(setID: UInt32): &Set {
912            pre {
913                Crowdflix.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
914            }
915
916            // Get a reference to the Set and return it
917            // use `&` to indicate the reference to the object and type
918            return (&Crowdflix.sets[setID] as &Set?)!
919        }
920
921        // startNewSeries ends the current series by incrementing
922        // the series number, meaning that Moments minted after this
923        // will use the new series number
924        //
925        // Returns: The new series number
926        //
927        access(all) fun startNewSeries(): UInt32 {
928            // End the current series and start a new one
929            // by incrementing the Crowdflix series number
930            Crowdflix.currentSeries = Crowdflix.currentSeries + UInt32(1)
931
932            emit NewSeriesStarted(newCurrentSeries: Crowdflix.currentSeries)
933
934            return Crowdflix.currentSeries
935        }
936
937        // createSubeditionResource creates new SubeditionMap resource that
938        // will be used to mint Moments with Subeditions
939        access(all) fun createSubeditionAdminResource() {
940            Crowdflix.account.storage.save<@SubeditionAdmin>(<- create SubeditionAdmin(), to: Crowdflix.SubeditionAdminStoragePath())
941        }
942
943        // setMomentsSubedition saves which Subedition the Moment belongs to
944        //
945        // Parameters: nftID: The ID of the NFT
946        //             subeditionID: The ID of the Subedition the Moment belongs to
947        //             setID: The ID of the Set that the Moment references
948        //             movieSceneID: The ID of the MovieScene that the Moment references
949        //
950        access(all) fun setMomentsSubedition(nftID: UInt64, subeditionID: UInt32, setID: UInt32, movieSceneID: UInt32) {
951            let subeditionAdmin = Crowdflix.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
952                ?? panic("No subedition admin resource in storage")
953
954            subeditionAdmin.setMomentsSubedition(nftID: nftID, subeditionID: subeditionID, setID: setID, movieSceneID: movieSceneID)
955        }
956
957        // createSubedition creates a new Subedition struct
958        // and stores it in the Subeditions dictionary in the SubeditionAdmin resource
959        //
960        // Parameters: name: The name of the Subedition
961        //             metadata: A dictionary mapping metadata titles to their data
962        //
963        // Returns: the ID of the new Subedition object
964        //
965        access(all) fun createSubedition(name: String, metadata: {String: String}): UInt32 {
966            let subeditionAdmin = Crowdflix.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
967                ?? panic("No subedition admin resource in storage")
968
969            return subeditionAdmin.createSubedition(name:name, metadata:metadata)
970        }
971
972        // createNewAdmin creates a new Admin resource
973        //
974        access(all) fun createNewAdmin(): @Admin {
975            return <- create Admin()
976        }
977    }
978
979    // This is the interface that users can cast their Moment Collection as
980    // to allow others to deposit Moments into their Collection. It also allows for reading
981    // the IDs of Moments in the Collection.
982    /// Deprecated: This is no longer used for defining access control anymore.
983    access(all) resource interface MomentCollectionPublic : NonFungibleToken.CollectionPublic {
984        access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection})
985        access(all) fun borrowMoment(id: UInt64): &NFT? {
986            // If the result isn't nil, the id of the returned reference
987            // should be the same as the argument to the function
988            post {
989                (result == nil) || (result?.id == id):
990                    "Cannot borrow Moment reference: The ID of the returned reference is incorrect"
991            }
992        }
993    }
994
995    // Collection is a resource that every user who owns NFTs
996    // will store in their account to manage their NFTS
997    //
998    access(all) resource Collection: MomentCollectionPublic, NonFungibleToken.Collection {
999        // Dictionary of Moment conforming tokens
1000        // NFT is a resource type with a UInt64 ID field
1001        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
1002
1003        init() {
1004            self.ownedNFTs <- {}
1005        }
1006
1007        // Return a list of NFT types that this receiver accepts
1008        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
1009            let supportedTypes: {Type: Bool} = {}
1010            supportedTypes[Type<@NFT>()] = true
1011            return supportedTypes
1012        }
1013
1014        // Return whether or not the given type is accepted by the collection
1015        // A collection that can accept any type should just return true by default
1016        access(all) view fun isSupportedNFTType(type: Type): Bool {
1017            if type == Type<@NFT>() {
1018                return true
1019            }
1020            return false
1021        }
1022
1023        // Return the amount of NFTs stored in the collection
1024        access(all) view fun getLength(): Int {
1025            return self.ownedNFTs.length
1026        }
1027
1028        // Create an empty Collection for Crowdflix NFTs and return it to the caller
1029        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
1030            return <- Crowdflix.createEmptyCollection(nftType: Type<@NFT>())
1031        }
1032
1033        // withdraw removes an Moment from the Collection and moves it to the caller
1034        //
1035        // Parameters: withdrawID: The ID of the NFT
1036        // that is to be removed from the Collection
1037        //
1038        // returns: @NonFungibleToken.NFT the token that was withdrawn
1039        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
1040            // Borrow nft and check if locked
1041            let nft = self.borrowNFT(withdrawID)
1042                ?? panic("Cannot borrow: empty reference")
1043
1044
1045            // Remove the nft from the Collection
1046            let token <- self.ownedNFTs.remove(key: withdrawID)
1047                ?? panic("Cannot withdraw: Moment does not exist in the collection")
1048
1049            emit Withdraw(id: token.id, from: self.owner?.address)
1050
1051            // Return the withdrawn token
1052            return <- token
1053        }
1054
1055        // batchWithdraw withdraws multiple tokens and returns them as a Collection
1056        //
1057        // Parameters: ids: An array of IDs to withdraw
1058        //
1059        // Returns: @NonFungibleToken.Collection: A collection that contains
1060        //                                        the withdrawn moments
1061        //
1062        access(NonFungibleToken.Withdraw) fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} {
1063            // Create a new empty Collection
1064            var batchCollection <- create Collection()
1065
1066            // Iterate through the ids and withdraw them from the Collection
1067            for id in ids {
1068                batchCollection.deposit(token: <- self.withdraw(withdrawID: id))
1069            }
1070
1071            // Return the withdrawn tokens
1072            return <- batchCollection
1073        }
1074
1075        // deposit takes a Moment and adds it to the Collections dictionary
1076        //
1077        // Paramters: token: the NFT to be deposited in the collection
1078        //
1079        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
1080            // Cast the deposited token as a Crowdflix NFT to make sure
1081            // it is the correct type
1082            let token <- token as! @NFT
1083
1084            // Get the token's ID
1085            let id = token.id
1086
1087            // Add the new token to the dictionary
1088            let oldToken <- self.ownedNFTs[id] <- token
1089
1090            // Only emit a deposit event if the Collection
1091            // is in an account's storage
1092            if self.owner?.address != nil {
1093                emit Deposit(id: id, to: self.owner?.address)
1094            }
1095
1096            // Destroy the empty old token that was "removed"
1097            destroy oldToken
1098        }
1099
1100        // batchDeposit takes a Collection object as an argument
1101        // and deposits each contained NFT into this Collection
1102        access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) {
1103            // Get an array of the IDs to be deposited
1104            let keys = tokens.getIDs()
1105
1106            // Iterate through the keys in the collection and deposit each one
1107            for key in keys {
1108                self.deposit(token: <- tokens.withdraw(withdrawID: key))
1109            }
1110
1111            // Destroy the empty Collection
1112            destroy tokens
1113        }
1114
1115        // destroyMoments destroys moments in this collection
1116        // unlocks the moments if they are locked
1117        //
1118        // Parameters: ids: An array of NFT IDs
1119        // to be destroyed from the Collection
1120        access(NonFungibleToken.Update) fun destroyMoments(ids: [UInt64]) {
1121
1122            for id in ids {
1123                // Remove the nft from the Collection
1124                let token <- self.ownedNFTs.remove(key: id)
1125                    ?? panic("Cannot destroy: Moment does not exist in collection: ".concat(id.toString()))
1126
1127                // Emit a withdraw event here so that platforms do not have to understand Crowdflix-specific events to see ownership change
1128                // A withdraw without a corresponding deposit means the NFT in question has no owner address
1129                emit Withdraw(id: id, from: self.owner?.address)
1130
1131
1132                destroy token
1133            }
1134        }
1135
1136        // getIDs returns an array of the IDs that are in the Collection
1137        access(all) view fun getIDs(): [UInt64] {
1138            return self.ownedNFTs.keys
1139        }
1140
1141        // borrowNFT Returns a borrowed reference to a Moment in the Collection
1142        // so that the caller can read its ID
1143        //
1144        // Parameters: id: The ID of the NFT to get the reference for
1145        //
1146        // Returns: A reference to the NFT
1147        //
1148        // Note: This only allows the caller to read the ID of the NFT,
1149        // not any crowdflix specific data. Please use borrowMoment to
1150        // read Moment data.
1151        //
1152        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
1153            return &self.ownedNFTs[id]
1154        }
1155
1156        // borrowMoment returns a borrowed reference to a Moment
1157        // so that the caller can read data and call methods from it.
1158        // They can use this to read its setID, movieSceneID, serialNumber,
1159        // or any of the setData or MovieScene data associated with it by
1160        // getting the setID or movieSceneID and reading those fields from
1161        // the smart contract.
1162        //
1163        // Parameters: id: The ID of the NFT to get the reference for
1164        //
1165        // Returns: A reference to the NFT
1166        access(all) view fun borrowMoment(id: UInt64): &NFT? {
1167            return self.borrowNFT(id) as! &NFT?
1168        }
1169
1170        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
1171            if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
1172                return nft as &{ViewResolver.Resolver}
1173            }
1174            return nil
1175        }
1176    }
1177
1178    // -----------------------------------------------------------------------
1179    // Crowdflix contract-level function definitions
1180    // -----------------------------------------------------------------------
1181
1182    // createEmptyCollection creates a new, empty Collection object so that
1183    // a user can store it in their account storage.
1184    // Once they have a Collection in their storage, they are able to receive
1185    // Moments in transactions.
1186    //
1187    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
1188        if nftType != Type<@NFT>() {
1189            panic("NFT type is not supported")
1190        }
1191        return <- create Crowdflix.Collection()
1192    }
1193
1194    // getAllMovieScenes returns all the movieScenes in crowdflix
1195    //
1196    // Returns: An array of all the movieScenes that have been created
1197    access(all) view fun getAllMovieScenes(): [MovieScene] {
1198        return Crowdflix.movieSceneDatas.values
1199    }
1200
1201    // getMovieSceneMetaData returns all the metadata associated with a specific MovieScene
1202    //
1203    // Parameters: movieSceneID: The id of the MovieScene that is being searched
1204    //
1205    // Returns: The metadata as a String to String mapping optional
1206    access(all) view fun getMovieSceneMetaData(movieSceneID: UInt32): {String: String}? {
1207        return self.movieSceneDatas[movieSceneID]?.metadata
1208    }
1209
1210    // getMovieSceneMetaDataByField returns the metadata associated with a
1211    //                        specific field of the metadata
1212    //                        Ex: field: "contentType" will return something
1213    //                        like "movie" or "tv show"
1214    //
1215    // Parameters: movieSceneID: The id of the MovieScene that is being searched
1216    //             field: The field to search for
1217    //
1218    // Returns: The metadata field as a String Optional
1219    access(all) view fun getMovieSceneMetaDataByField(movieSceneID: UInt32, field: String): String? {
1220        // Don't force a revert if the movieSceneID or field is invalid
1221        if let movieScene = Crowdflix.movieSceneDatas[movieSceneID] {
1222            return movieScene.metadata[field]
1223        }
1224        return nil
1225    }
1226
1227    // getSetData returns the data that the specified Set
1228    //            is associated with.
1229    //
1230    // Parameters: setID: The id of the Set that is being searched
1231    //
1232    // Returns: The QuerySetData struct that has all the important information about the set
1233    access(all) fun getSetData(setID: UInt32): QuerySetData? {
1234        if Crowdflix.sets[setID] == nil {
1235            return nil
1236        }
1237        return QuerySetData(setID: setID)
1238    }
1239
1240    // getSetName returns the name that the specified Set
1241    //            is associated with.
1242    //
1243    // Parameters: setID: The id of the Set that is being searched
1244    //
1245    // Returns: The name of the Set
1246    access(all) view fun getSetName(setID: UInt32): String? {
1247        // Don't force a revert if the setID is invalid
1248        return Crowdflix.setDatas[setID]?.name
1249    }
1250
1251    // getSetSeries returns the series that the specified Set
1252    //              is associated with.
1253    //
1254    // Parameters: setID: The id of the Set that is being searched
1255    //
1256    // Returns: The series that the Set belongs to
1257    access(all) view fun getSetSeries(setID: UInt32): UInt32? {
1258        // Don't force a revert if the setID is invalid
1259        return Crowdflix.setDatas[setID]?.series
1260    }
1261
1262    // getSetIDsByName returns the IDs that the specified Set name
1263    //                 is associated with.
1264    //
1265    // Parameters: setName: The name of the Set that is being searched
1266    //
1267    // Returns: An array of the IDs of the Set if it exists, or nil if doesn't
1268    access(all) fun getSetIDsByName(setName: String): [UInt32]? {
1269        var setIDs: [UInt32] = []
1270
1271        // Iterate through all the setDatas and search for the name
1272        for setData in Crowdflix.setDatas.values {
1273            if setName == setData.name {
1274                // If the name is found, return the ID
1275                setIDs.append(setData.setID)
1276            }
1277        }
1278
1279        // If the name isn't found, return nil
1280        // Don't force a revert if the setName is invalid
1281        if setIDs.length == 0 {
1282            return nil
1283        }
1284        return setIDs
1285    }
1286
1287    // getMovieScenesInSet returns the list of MovieScene IDs that are in the Set
1288    //
1289    // Parameters: setID: The id of the Set that is being searched
1290    //
1291    // Returns: An array of MovieScene IDs
1292    access(all) view fun getMovieScenesInSet(setID: UInt32): [UInt32]? {
1293        // Don't force a revert if the setID is invalid
1294        return Crowdflix.sets[setID]?.movieScenes
1295    }
1296
1297    // isEditionRetired returns a boolean that indicates if a Set/MovieScene combo
1298    //                  (otherwise known as an edition) is retired.
1299    //                  If an edition is retired, it still remains in the Set,
1300    //                  but Moments can no longer be minted from it.
1301    //
1302    // Parameters: setID: The id of the Set that is being searched
1303    //             movieSceneID: The id of the MovieScene that is being searched
1304    //
1305    // Returns: Boolean indicating if the edition is retired or not
1306    access(all) fun isEditionRetired(setID: UInt32, movieSceneID: UInt32): Bool? {
1307        // Return the retired status for the MovieScene in the set if it exists
1308        if let setdata = self.getSetData(setID: setID) {
1309            return setdata.getRetired()[movieSceneID]
1310        }
1311        return nil
1312    }
1313
1314    // isSetLocked returns a boolean that indicates if a Set
1315    //             is locked. If it's locked,
1316    //             new MovieScenes can no longer be added to it,
1317    //             but Moments can still be minted from MovieScenes the set contains.
1318    //
1319    // Parameters: setID: The id of the Set that is being searched
1320    //
1321    // Returns: Boolean indicating if the Set is locked or not
1322    access(all) view fun isSetLocked(setID: UInt32): Bool? {
1323        // Don't force a revert if the setID is invalid
1324        return Crowdflix.sets[setID]?.locked
1325    }
1326
1327    // getNumMomentsInEdition return the number of Moments that have been
1328    //                        minted from a certain edition.
1329    //
1330    // Parameters: setID: The id of the Set that is being searched
1331    //             movieSceneID: The id of the MovieScene that is being searched
1332    //
1333    // Returns: The total number of Moments
1334    //          that have been minted from an edition
1335    access(all) fun getNumMomentsInEdition(setID: UInt32, movieSceneID: UInt32): UInt32? {
1336        // Return the number of moments minted for the MovieScene in the set if it exists
1337        if let setdata = self.getSetData(setID: setID) {
1338            return setdata.getNumberMintedPerMovieScene()[movieSceneID]
1339        }
1340        return nil
1341    }
1342
1343    // getMomentsSubedition returns the Subedition the Moment belongs to
1344    //
1345    // Parameters: nftID: The ID of the NFT
1346    //
1347    // returns: UInt32? Subedition's ID if exists
1348    //
1349    access(all) view fun getMomentsSubedition(nftID: UInt64): UInt32? {
1350        let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
1351            ?? panic("No subedition admin resource in storage")
1352        return subeditionAdmin.getMomentsSubedition(nftID: nftID)
1353    }
1354
1355    // getAllSubeditions returns all the subeditions in crowdflix subeditionAdmin resource
1356    //
1357    // Returns: An array of all the subeditions that have been created
1358    access(all) view fun getAllSubeditions(): &[Subedition] {
1359        let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
1360            ?? panic("No subedition admin resource in storage")
1361        return subeditionAdmin.subeditionDatas.values
1362    }
1363
1364    // getSubeditionByID returns the subedition struct entity
1365    //
1366    // Parameters: subeditionID: The id of the Subedition that is being searched
1367    //
1368    // Returns: The Subedition struct
1369    access(all) view fun getSubeditionByID(subeditionID: UInt32): &Subedition {
1370        let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
1371            ?? panic("No subedition admin resource in storage")
1372        return subeditionAdmin.subeditionDatas[subeditionID]!
1373    }
1374
1375    // getSubeditionByNFTID returns the subedition struct that the NFT belongs to
1376    //
1377    // Parameters: nftID: The id of the NFT that is being searched
1378    //
1379    // Returns: The subedition struct that the NFT belongs to
1380    access(all) view fun getSubeditionByNFTID(_ nftID: UInt64): &Subedition? {
1381        if let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath()) {
1382            if let subeditionID = subeditionAdmin.getMomentsSubedition(nftID: nftID) {
1383                return subeditionAdmin.subeditionDatas[subeditionID]
1384            }
1385        }
1386        return nil
1387    }
1388
1389    // This script reads the public nextSubeditionID from the SubeditionAdmin resource and
1390    // returns that number to the caller
1391    //
1392    // Returns: UInt32
1393    // the next number in nextSubeditionID from the SubeditionAdmin resource
1394    access(all) view fun getNextSubeditionID(): UInt32 {
1395        let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
1396            ?? panic("No subedition admin resource in storage")
1397        return subeditionAdmin.nextSubeditionID
1398    }
1399
1400    // SubeditionAdmin is a resource that allows Set to mint Moments with Subeditions
1401    //
1402    access(all) struct Subedition {
1403        access(all) let subeditionID: UInt32
1404
1405        access(all) let name: String
1406
1407        access(all) let metadata: {String: String}
1408
1409        init(subeditionID: UInt32, name: String, metadata: {String: String}) {
1410            pre {
1411                name.length != 0: "New Subedition name cannot be empty"
1412            }
1413            self.subeditionID = subeditionID
1414            self.name = name
1415            self.metadata = metadata
1416        }
1417    }
1418
1419    access(all) resource SubeditionAdmin {
1420        // Map of number of already minted Moments using Subedition.
1421        // When a new Moment with Subedition is minted, 1 is added to the
1422        // number in this map by the key, formed by concatinating of
1423        // SetID, MovieSceneID and SubeditionID
1424        access(contract) let numberMintedPerSubedition: {String: UInt32}
1425
1426        // Map of Subedition which the Moment belongs to.
1427        // This map updates after each minting.
1428        access(contract) let momentsSubedition: {UInt64: UInt32}
1429
1430        // The ID that is used to create Subeditions.
1431        // Every time a Subeditions is created, subeditionID is assigned
1432        // to the new Subedition's ID and then is incremented by 1.
1433        access(contract) var nextSubeditionID: UInt32
1434
1435        // Variable size dictionary of Subedition structs
1436        access(contract) let subeditionDatas: {UInt32: Subedition}
1437
1438        // createSubedition creates a new Subedition struct
1439        // and stores it in the Subeditions dictionary in the SubeditionAdmin resource
1440        //
1441        // Parameters: name: The name of the Subedition
1442        //             metadata: A dictionary mapping metadata titles to their data
1443        //
1444        // Returns: the ID of the new Subedition object
1445        //
1446        access(all) fun createSubedition(name: String, metadata: {String: String}): UInt32 {
1447            let newID = self.nextSubeditionID
1448
1449            var newSubedition = Subedition(subeditionID: newID, name: name, metadata: metadata)
1450
1451            self.nextSubeditionID = self.nextSubeditionID + UInt32(1)
1452
1453            self.subeditionDatas[newID] = newSubedition
1454
1455            emit SubeditionCreated(subeditionID: newID, name: name, metadata: metadata)
1456
1457            return newID
1458        }
1459
1460        // getMomentsSubedition function that return's wich Subedition the Moment belongs to
1461        //
1462        // Parameters: nftID: The ID of the NFT
1463        //
1464        // returns: UInt32? Subedition's ID if exists
1465        //
1466        access(all) view fun getMomentsSubedition(nftID: UInt64): UInt32? {
1467            return self.momentsSubedition[nftID]
1468        }
1469
1470        // getNumberMintedPerSubedition function that return's
1471        // the number of Moments that have been minted for this subedition
1472        // to use as this Moment's serial number
1473        //
1474        // Parameters: setID: The ID of the Set Moment will be minted from
1475        //             movieSceneID: The ID of the MovieScene Moment will be minted from
1476        //             subeditionID: The ID of the Subedition using which moment will be minted
1477        //
1478        // returns: UInt32 Number of Moments, already minted for this Subedition
1479        //
1480        access(all) fun getNumberMintedPerSubedition(setID: UInt32, movieSceneID: UInt32, subeditionID: UInt32): UInt32 {
1481            let setPlaySubedition = self.getSetPlaySubeditionString(setID, movieSceneID, subeditionID)
1482            if !self.numberMintedPerSubedition.containsKey(setPlaySubedition) {
1483                self.numberMintedPerSubedition.insert(key: setPlaySubedition, UInt32(0))
1484                return UInt32(0)
1485            }
1486            return self.numberMintedPerSubedition[setPlaySubedition]!
1487        }
1488
1489        // addToNumberMintedPerSubedition function that increments 1 to the
1490        // number of Moments that have been minted for this subedition
1491        //
1492        // Parameters: setID: The ID of the Set Moment will be minted from
1493        //             movieSceneID: The ID of the MovieScene Moment will be minted from
1494        //             subeditionID: The ID of the Subedition using which moment will be minted
1495        //
1496        //
1497        access(contract) fun addToNumberMintedPerSubedition(setID: UInt32, movieSceneID: UInt32, subeditionID: UInt32) {
1498            let setPlaySubedition = self.getSetPlaySubeditionString(setID, movieSceneID, subeditionID)
1499
1500            // Get number of moments minted for this subedition
1501            let numberMinted = self.numberMintedPerSubedition[setPlaySubedition]
1502                ?? panic("Could not find number of moments minted for specified Subedition!")
1503
1504            // Increment the number of moments minted for this subedition
1505            self.numberMintedPerSubedition[setPlaySubedition] = numberMinted + UInt32(1)
1506        }
1507
1508        // getSetPlaySubeditionString builds a string that is used as a key in the numberMintedPerSubedition map
1509        access(self) view fun getSetPlaySubeditionString(_ setID: UInt32, _ movieSceneID: UInt32, _ subeditionID: UInt32): String {
1510            return setID.toString().concat(movieSceneID.toString()).concat(subeditionID.toString())
1511        }
1512
1513
1514        // setMomentsSubedition saves which Subedition the Moment belongs to
1515        //
1516        // Parameters: nftID: The ID of the NFT
1517        //             subeditionID: The ID of the Subedition the Moment belongs to
1518        //             setID: The ID of the Set that the Moment references
1519        //             movieSceneID: The ID of the MovieScene that the Moment references
1520        //
1521        access(all) fun setMomentsSubedition(nftID: UInt64, subeditionID: UInt32, setID: UInt32, movieSceneID: UInt32) {
1522            pre {
1523                !self.momentsSubedition.containsKey(nftID) : "Subedition for this moment already exists!"
1524            }
1525
1526            self.momentsSubedition.insert(key: nftID, subeditionID)
1527
1528            emit SubeditionAddedToMoment(momentID: nftID, subeditionID: subeditionID, setID: setID, movieSceneID: movieSceneID)
1529        }
1530
1531        init() {
1532            self.momentsSubedition = {}
1533            self.numberMintedPerSubedition = {}
1534            self.subeditionDatas = {}
1535            self.nextSubeditionID = 1
1536        }
1537    }
1538
1539    //------------------------------------------------------------
1540    // Contract MetadataViews
1541    //------------------------------------------------------------
1542
1543    // getContractViews returns the metadata view types available for this contract
1544    access(all) view fun getContractViews(resourceType: Type?): [Type] {
1545        return [
1546            Type<MetadataViews.NFTCollectionData>(),
1547            Type<MetadataViews.NFTCollectionDisplay>(),
1548            Type<MetadataViews.Royalties>()
1549        ]
1550    }
1551
1552    // resolveContractView resolves this contract's metadata views
1553    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
1554        post {
1555            result == nil || result!.getType() == viewType: "The returned view must be of the given type or nil"
1556        }
1557        switch viewType {
1558            case Type<MetadataViews.NFTCollectionData>():
1559                return MetadataViews.NFTCollectionData(
1560                    storagePath: /storage/MomentCollection,
1561                    publicPath: /public/MomentCollection,
1562                    publicCollection: Type<&Collection>(),
1563                    publicLinkedType: Type<&Collection>(),
1564                    createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
1565                        return <- Crowdflix.createEmptyCollection(nftType: Type<@NFT>())
1566                    })
1567                )
1568            case Type<MetadataViews.NFTCollectionDisplay>():
1569                let bannerImage = MetadataViews.Media(
1570                    file: MetadataViews.HTTPFile(
1571                        url: "https://marketplace.crowdflix.io/images/crowdflix-logo-horizontal-white.svg"
1572                    ),
1573                    mediaType: "image/svg+xml"
1574                )
1575                let squareImage = MetadataViews.Media(
1576                    file: MetadataViews.HTTPFile(
1577                        url: "https://marketplace.crowdflix.io/images/favicon.svg"
1578                    ),
1579                    mediaType: "image/svg+xml"
1580                )
1581                return MetadataViews.NFTCollectionDisplay(
1582                    name: "Crowdflix",
1583                    description: "Crowdflix is your gateway to own, trade, and collect iconic moments from movies, TV shows, anime, and more — all as limited-edition digital collectibles. Relive legendary scenes and build your cinematic NFT collection.",
1584                    externalURL: MetadataViews.ExternalURL("https://marketplace.crowdflix.io"),
1585                    squareImage: squareImage,
1586                    bannerImage: bannerImage,
1587                    socials: {
1588                        "twitter": MetadataViews.ExternalURL("https://twitter.com/crowdflix")
1589                    }
1590                )
1591            case Type<MetadataViews.Royalties>():
1592                let royaltyReceiver: Capability<&{FungibleToken.Receiver}> =
1593                    getAccount(Crowdflix.RoyaltyAddress()).capabilities.get<&{FungibleToken.Receiver}>(MetadataViews.getRoyaltyReceiverPublicPath())!
1594                return MetadataViews.Royalties(
1595                    [
1596                        MetadataViews.Royalty(
1597                            receiver: royaltyReceiver,
1598                            cut: 0.05,
1599                            description: "Crowdflix marketplace royalty"
1600                        )
1601                    ]
1602                )
1603        }
1604        return nil
1605    }
1606
1607    // -----------------------------------------------------------------------
1608    // Crowdflix initialization function
1609    // -----------------------------------------------------------------------
1610    //
1611    init() {
1612        // Initialize contract fields
1613        self.currentSeries = 0
1614        self.movieSceneDatas = {}
1615        self.setDatas = {}
1616        self.sets <- {}
1617        self.nextMovieSceneID = 1
1618        self.nextSetID = 1
1619        self.totalSupply = 0
1620
1621        // Put a new Collection in storage
1622        self.account.storage.save<@Collection>(<- create Collection(), to: /storage/MomentCollection)
1623
1624        // Create and publish a capability for the collection
1625        self.account.capabilities.publish(
1626            self.account.capabilities.storage.issue<&Collection>(/storage/MomentCollection),
1627            at: /public/MomentCollection
1628        )
1629
1630        // Put the Minter in storage
1631        self.account.storage.save<@Admin>(<- create Admin(), to: /storage/CrowdflixAdmin)
1632    }
1633}
1634