Smart Contract
Crowdflix
A.ad9300d3ca41f63a.Crowdflix
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