DeploySEALED
&○□!○~@$~&#■@◆▓●~?◇╱◆▪▫●@!○%$*$░○!░○█╲▫╱!○▓◆░?▓*▒░●%◆●◇*@?!◇~□▫▫
Transaction ID
Execution Fee
0.00000724 FLOWTransaction Summary
DeployContract deployment
Contract deployment
Script Arguments
0nameString
Crowdflix
1codeString
/*
Description: Central Smart Contract for Crowdflix NFT Platform
This smart contract defines the primary logic for the Crowdflix platform —
a Flow-based NFT marketplace for collectible cinematic moments from movies,
series and more.
The contract manages metadata and minting rights for MovieScenes and Sets,
which act as templates for minting collectible Moments (NFTs).
When a new MovieScene wants to be added to the records, an Admin creates
a new MovieScene struct that is stored in the smart contract.
Then an Admin can create new Sets. Sets consist of a public struct that
contains public information about a set, and a private resource used
to mint new moments based off of movieScenes that have been linked to the Set.
The admin resource has the power to do all of the important actions
in the smart contract. When admins want to call functions in a set,
they call their borrowSet function to get a reference
to a set in the contract. Then, they can call functions on the set using that reference.
In this way, the smart contract and its defined resources interact
with great teamwork.
When moments are minted, they are initialized with a MomentData struct and
are returned by the minter.
The contract also defines a Collection resource. This is an object that
every Crowdflix NFT owner will store in their account
to manage their NFT collection.
The main Crowdflix account will also have its own Moment collections
it can use to hold its own moments that have not yet been sent to a user.
Note: All state changing functions will panic if an invalid argument is
provided or one of its pre-conditions or post conditions aren't met.
Functions that don't modify state will simply return 0 or nil
and those cases need to be handled by the caller.
*/
// Testnet
// import FungibleToken from 0x9a0766d93b6608b7
// import NonFungibleToken from 0x631e88ae7f1d7c20
// import MetadataViews from 0x631e88ae7f1d7c20
// import ViewResolver from 0x631e88ae7f1d7c20
// Production
import FungibleToken from 0xf233dcee88fe0abe
import NonFungibleToken from 0x1d7e57aa55817448
import MetadataViews from 0x1d7e57aa55817448
import ViewResolver from 0x1d7e57aa55817448
access(all) contract Crowdflix: NonFungibleToken {
// -----------------------------------------------------------------------
// Crowdflix deployment variables
// -----------------------------------------------------------------------
// The network the contract is deployed on
access(all) view fun Network(): String { return "production" }
// The address to which royalties should be deposited
access(all) view fun RoyaltyAddress(): Address { return 0x474b92dbf17fda56 }
// The path to the Subedition Admin resource belonging to the Account
// which the contract is deployed on
access(all) view fun SubeditionAdminStoragePath(): StoragePath { return /storage/CrowdflixSubeditionAdmin}
// -----------------------------------------------------------------------
// Crowdflix contract Events
// -----------------------------------------------------------------------
// Emitted when a new MovieScene struct is created
access(all) event MovieSceneCreated(id: UInt32, metadata: {String: String})
// Emitted when a new series has been triggered by an admin
access(all) event NewSeriesStarted(newCurrentSeries: UInt32)
// Events for Set-Related actions
//
// Emitted when a new Set is created
access(all) event SetCreated(setID: UInt32, series: UInt32)
// Emitted when a new MovieScene is added to a Set
access(all) event MovieSceneAddedToSet(setID: UInt32, movieSceneID: UInt32)
// Emitted when a MovieScene is retired from a Set and cannot be used to mint
access(all) event MovieSceneRetiredFromSet(setID: UInt32, movieSceneID: UInt32, numMoments: UInt32)
// Emitted when a Set is locked, meaning MovieScenes cannot be added
access(all) event SetLocked(setID: UInt32)
// Emitted when a Moment is minted from a Set
access(all) event MomentMinted(momentID: UInt64, movieSceneID: UInt32, setID: UInt32, serialNumber: UInt32, subeditionID: UInt32)
// Events for Collection-related actions
//
// Emitted when a moment is withdrawn from a Collection
access(all) event Withdraw(id: UInt64, from: Address?)
// Emitted when a moment is deposited into a Collection
access(all) event Deposit(id: UInt64, to: Address?)
// Emitted when a Moment is destroyed
access(all) event MomentDestroyed(id: UInt64)
// Emitted when a Subedition is created
access(all) event SubeditionCreated(subeditionID: UInt32, name: String, metadata: {String: String})
// Emitted when a Subedition is linked to the specific Moment
access(all) event SubeditionAddedToMoment(momentID: UInt64, subeditionID: UInt32, setID: UInt32, movieSceneID: UInt32)
// -----------------------------------------------------------------------
// Crowdflix contract-level fields.
// These contain actual values that are stored in the smart contract.
// -----------------------------------------------------------------------
// Series that this Set belongs to.
// Series is a concept that indicates a group of Sets through time.
// Many Sets can exist at a time, but only one series.
access(all) var currentSeries: UInt32
// Variable size dictionary of MovieScene structs
access(self) var movieSceneDatas: {UInt32: MovieScene}
// Variable size dictionary of SetData structs
access(self) var setDatas: {UInt32: SetData}
// Variable size dictionary of Set resources
access(self) var sets: @{UInt32: Set}
// The ID that is used to create MovieScenes.
// Every time a MovieScene is created, movieSceneID is assigned
// to the new MovieScene's ID and then is incremented by 1.
access(all) var nextMovieSceneID: UInt32
// The ID that is used to create Sets. Every time a Set is created
// setID is assigned to the new set's ID and then is incremented by 1.
access(all) var nextSetID: UInt32
// The total number of Crowdflix NFTs that have been created
// Because NFTs can be destroyed, it doesn't necessarily mean that this
// reflects the total number of NFTs in existence, just the number that
// have been minted to date. Also used as global moment IDs for minting.
access(all) var totalSupply: UInt64
// -----------------------------------------------------------------------
// Crowdflix contract-level Composite Type definitions
// -----------------------------------------------------------------------
// These are just *definitions* for Types that this contract
// and other accounts can use. These definitions do not contain
// actual stored values, but an instance (or object) of one of these Types
// can be created by this contract that contains stored values.
// -----------------------------------------------------------------------
// MovieScene is a Struct that holds metadata associated
// with a specific movie scene, like the legendary scene when
// Iron Man snapped his fingers in Avengers Endgame
// or when Neo chose the red pill in The Matrix.
//
// Moment NFTs will all reference a single MovieScene as the owner of
// its metadata. The MovieScene are publicly accessible, so anyone can
// read the metadata associated with a specific MovieScene ID
//
access(all) struct MovieScene {
// The unique ID for the MovieScene
access(all) let movieSceneID: UInt32
// Stores all the metadata about the MovieScene as a string mapping
// This is not the long term way NFT metadata will be stored. It's a temporary
// construct while we figure out a better way to do metadata.
//
access(all) let metadata: {String: String}
init(metadata: {String: String}) {
pre {
metadata.length != 0: "New MovieScene metadata cannot be empty"
}
self.movieSceneID = Crowdflix.nextMovieSceneID
self.metadata = metadata
}
}
// A Set is a grouping of MovieScenes that have occured in the real world
// that make up a related group of collectibles, like sets of baseball
// or Magic cards. A MovieScene can exist in multiple different sets.
//
// SetData is a struct that is stored in a field of the contract.
// Anyone can query the constant information
// about a set by calling various getters located
// at the end of the contract. Only the admin has the ability
// to modify any data in the private Set resource.
//
access(all) struct SetData {
// Unique ID for the Set
access(all) let setID: UInt32
// Name of the Set
// ex. "Times when Marvel heroes made terrible decisions"
access(all) let name: String
// Series that this Set belongs to.
// Series is a concept that indicates a group of Sets through time.
// Many Sets can exist at a time, but only one series.
access(all) let series: UInt32
init(name: String) {
pre {
name.length > 0: "New Set name cannot be empty"
}
self.setID = Crowdflix.nextSetID
self.name = name
self.series = Crowdflix.currentSeries
}
}
// Set is a resource type that contains the functions to add and remove
// MovieScenes from a set and mint Moments.
//
// It is stored in a private field in the contract so that
// the admin resource can call its methods.
//
// The admin can add MovieScenes to a Set so that the set can mint Moments
// that reference that playdata.
// The Moments that are minted by a Set will be listed as belonging to
// the Set that minted it, as well as the MovieScenes it references.
//
// Admin can also retire MovieScenes from the Set, meaning that the retired
// MovieScene can no longer have Moments minted from it.
//
// If the admin locks the Set, no more MovieScenes can be added to it, but
// Moments can still be minted.
//
// If retireAll() and lock() are called back-to-back,
// the Set is closed off forever and nothing more can be done with it.
access(all) resource Set {
// Unique ID for the set
access(all) let setID: UInt32
// Array of MovieScenes that are a part of this set.
// When a MovieScene is added to the set, its ID gets appended here.
// The ID does not get removed from this array when a MovieScene is retired.
access(contract) var movieScenes: [UInt32]
// Map of MovieScene IDs that Indicates if a MovieScene in this Set can be minted.
// When a MovieScene is added to a Set, it is mapped to false (not retired).
// When a MovieScene is retired, this is set to true and cannot be changed.
access(contract) var retired: {UInt32: Bool}
// Indicates if the Set is currently locked.
// When a Set is created, it is unlocked
// and MovieScenes are allowed to be added to it.
// When a set is locked, MovieScenes cannot be added.
// A Set can never be changed from locked to unlocked,
// the decision to lock a Set it is final.
// If a Set is locked, MovieScenes cannot be added, but
// Moments can still be minted from MovieScenes
// that exist in the Set.
access(all) var locked: Bool
// Mapping of MovieScene IDs that indicates the number of Moments
// that have been minted for specific MovieScenes in this Set.
// When a Moment is minted, this value is stored in the Moment to
// show its place in the Set, eg. 13 of 60.
access(contract) var numberMintedPerMovieScene: {UInt32: UInt32}
init(name: String) {
self.setID = Crowdflix.nextSetID
self.movieScenes = []
self.retired = {}
self.locked = false
self.numberMintedPerMovieScene = {}
// Create a new SetData for this Set and store it in contract storage
Crowdflix.setDatas[self.setID] = SetData(name: name)
}
// addMovieScene adds a MovieScene to the set
//
// Parameters: movieSceneID: The ID of the MovieScene that is being added
//
// Pre-Conditions:
// The MovieScene needs to be an existing
// The Set needs to be not locked
// The MovieScene can't have already been added to the Set
//
access(all) fun addMovieScene(movieSceneID: UInt32) {
pre {
Crowdflix.movieSceneDatas[movieSceneID] != nil: "Cannot add the MovieScene to Set: MovieScene doesn't exist."
!self.locked: "Cannot add the MovieScene to the Set after the set has been locked."
self.numberMintedPerMovieScene[movieSceneID] == nil: "The MovieScene has already beed added to the set."
}
// Add the MovieScene to the array of Plays
self.movieScenes.append(movieSceneID)
// Open the MovieScene up for minting
self.retired[movieSceneID] = false
// Initialize the Moment count to zero
self.numberMintedPerMovieScene[movieSceneID] = 0
emit MovieSceneAddedToSet(setID: self.setID, movieSceneID: movieSceneID)
}
// addMovieScenes adds multiple MovieScenes to the Set
//
// Parameters: movieSceneIDs: The IDs of the MovieScenes that are being added
// as an array
//
access(all) fun addMovieScenes(movieSceneIDs: [UInt32]) {
for movieScene in movieSceneIDs {
self.addMovieScene(movieSceneID: movieScene)
}
}
// retireMovieScene retires a MovieScene from the Set so that it can't mint new Moments
//
// Parameters: movieSceneID: The ID of the MovieScene that is being retired
//
// Pre-Conditions:
// The MovieScene is part of the Set and not retired (available for minting).
//
access(all) fun retireMovieScene(movieSceneID: UInt32) {
pre {
self.retired[movieSceneID] != nil: "Cannot retire the MovieScene: MovieScene doesn't exist in this set!"
}
if !self.retired[movieSceneID]! {
self.retired[movieSceneID] = true
emit MovieSceneRetiredFromSet(setID: self.setID, movieSceneID: movieSceneID, numMoments: self.numberMintedPerMovieScene[movieSceneID]!)
}
}
// retireAll retires all the MovieScenes in the Set
// Afterwards, none of the retired MovieScenes will be able to mint new Moments
//
access(all) fun retireAll() {
for movieScene in self.movieScenes {
self.retireMovieScene(movieSceneID: movieScene)
}
}
// lock() locks the Set so that no more MovieScenes can be added to it
//
// Pre-Conditions:
// The Set should not be locked
access(all) fun lock() {
if !self.locked {
self.locked = true
emit SetLocked(setID: self.setID)
}
}
// mintMoment mints a new Moment and returns the newly minted Moment
//
// Parameters: movieSceneID: The ID of the MovieScene that the Moment references
//
// Pre-Conditions:
// The MovieScene must exist in the Set and be allowed to mint new Moments
//
// Returns: The NFT that was minted
//
access(all) fun mintMoment(movieSceneID: UInt32): @NFT {
pre {
self.retired[movieSceneID] != nil: "Cannot mint the moment: This MovieScene doesn't exist."
!self.retired[movieSceneID]!: "Cannot mint the moment from this MovieScene: This MovieScene has been retired."
}
// Gets the number of Moments that have been minted for this MovieScene
// to use as this Moment's serial number
let numInMovieScene = self.numberMintedPerMovieScene[movieSceneID]!
// Mint the new moment
let newMoment: @NFT <- create NFT(
serialNumber: numInMovieScene + UInt32(1),
movieSceneID: movieSceneID,
setID: self.setID,
subeditionID: 0
)
// Increment the count of Moments minted for this MovieScene
self.numberMintedPerMovieScene[movieSceneID] = numInMovieScene + UInt32(1)
return <-newMoment
}
// batchMintMoment mints an arbitrary quantity of Moments
// and returns them as a Collection
//
// Parameters: movieSceneID: the ID of the MovieScene that the Moments are minted for
// quantity: The quantity of Moments to be minted
//
// Returns: Collection object that contains all the Moments that were minted
//
access(all) fun batchMintMoment(movieSceneID: UInt32, quantity: UInt64): @Collection {
let newCollection <- create Collection()
var i: UInt64 = 0
while i < quantity {
newCollection.deposit(token: <-self.mintMoment(movieSceneID: movieSceneID))
i = i + UInt64(1)
}
return <- newCollection
}
// mintMomentWithSubedition mints a new Moment with subedition and returns the newly minted Moment
//
// Parameters: movieSceneID: The ID of the MovieScene that the Moment references
// subeditionID: The ID of the subedition within Edition that the Moment references
//
// Pre-Conditions:
// The MovieScene must exist in the Set and be allowed to mint new Moments
//
// Returns: The NFT that was minted
//
access(all) fun mintMomentWithSubedition(movieSceneID: UInt32, subeditionID: UInt32): @NFT {
pre {
self.retired[movieSceneID] != nil: "Cannot mint the moment: This MovieScene doesn't exist."
!self.retired[movieSceneID]!: "Cannot mint the moment from this MovieScene: This MovieScene has been retired."
}
// Gets the number of Moments that have been minted for this subedition
// to use as this Moment's serial number
let subeditionRef = Crowdflix.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
?? panic("No subedition admin resource in storage")
let numInSubedition = subeditionRef.getNumberMintedPerSubedition(
setID: self.setID,
movieSceneID: movieSceneID,
subeditionID: subeditionID
)
// Mint the new moment
let newMoment: @NFT <- create NFT(
serialNumber: numInSubedition + UInt32(1),
movieSceneID: movieSceneID,
setID: self.setID,
subeditionID: subeditionID
)
// Increment the count of Moments minted for this subedition
subeditionRef.addToNumberMintedPerSubedition(
setID: self.setID,
movieSceneID: movieSceneID,
subeditionID: subeditionID
)
subeditionRef.setMomentsSubedition(nftID: newMoment.id, subeditionID: subeditionID, setID: self.setID, movieSceneID: movieSceneID)
self.numberMintedPerMovieScene[movieSceneID] = self.numberMintedPerMovieScene[movieSceneID]! + UInt32(1)
return <- newMoment
}
// batchMintMomentWithSubedition mints an arbitrary quantity of Moments with subedition
// and returns them as a Collection
//
// Parameters: movieSceneID: the ID of the MovieScene that the Moments are minted for
// quantity: The quantity of Moments to be minted
// subeditionID: The ID of the subedition within Edition that the Moments references
//
// Returns: Collection object that contains all the Moments that were minted
//
access(all) fun batchMintMomentWithSubedition(movieSceneID: UInt32, quantity: UInt64, subeditionID: UInt32): @Collection {
let newCollection <- create Collection()
var i: UInt64 = 0
while i < quantity {
newCollection.deposit(token: <-self.mintMomentWithSubedition(movieSceneID: movieSceneID, subeditionID: subeditionID))
i = i + UInt64(1)
}
return <-newCollection
}
access(all) view fun getMovieScenes(): [UInt32] {
return self.movieScenes
}
access(all) view fun getRetired(): {UInt32: Bool} {
return self.retired
}
access(all) view fun getNumberMintedPerMovieScene(): {UInt32: UInt32} {
return self.numberMintedPerMovieScene
}
}
// Struct that contains all of the important data about a set
// Can be easily queried by instantiating the `QuerySetData` object
// with the desired set ID
// let setData = Crowdflix.QuerySetData(setID: 12)
//
access(all) struct QuerySetData {
access(all) let setID: UInt32
access(all) let name: String
access(all) let series: UInt32
access(self) var movieScenes: [UInt32]
access(self) var retired: {UInt32: Bool}
access(all) var locked: Bool
access(self) var numberMintedPerMovieScene: {UInt32: UInt32}
init(setID: UInt32) {
pre {
Crowdflix.sets[setID] != nil: "The set with the provided ID does not exist"
}
let set = (&Crowdflix.sets[setID] as &Set?)!
let setData = Crowdflix.setDatas[setID]!
self.setID = setID
self.name = setData.name
self.series = setData.series
self.movieScenes = set.getMovieScenes()
self.retired = set.getRetired()
self.locked = set.locked
self.numberMintedPerMovieScene = set.getNumberMintedPerMovieScene()
}
access(all) view fun getMovieScenes(): [UInt32] {
return self.movieScenes
}
access(all) view fun getRetired(): {UInt32: Bool} {
return self.retired
}
access(all) view fun getNumberMintedPerMovieScene(): {UInt32: UInt32} {
return self.numberMintedPerMovieScene
}
}
access(all) struct MomentData {
// The ID of the Set that the Moment comes from
access(all) let setID: UInt32
// The ID of the MovieScene that the Moment references
access(all) let movieSceneID: UInt32
// The place in the edition that this Moment was minted
// Otherwise know as the serial number
access(all) let serialNumber: UInt32
init(setID: UInt32, movieSceneID: UInt32, serialNumber: UInt32) {
self.setID = setID
self.movieSceneID = movieSceneID
self.serialNumber = serialNumber
}
}
// This is an implementation of a custom metadata view for MovieSceneMoment.
// This view contains the MovieScene metadata.
//
access(all) struct MovieSceneMomentMetadataView {
access(all) let title: String?
access(all) let sceneDescription: String?
access(all) let universe: String?
access(all) let studio: String?
access(all) let contentType: String?
access(all) let characterNames: String?
access(all) let actorNames: String?
access(all) let directorOrCreator: String?
access(all) let releaseYear: String?
access(all) let genre: String?
access(all) let dropDate: String?
access(all) let dateOfMoment: String?
access(all) let sceneCategory: String?
access(all) let sceneType: String?
access(all) let seriesNumber: UInt32?
access(all) let setName: String?
access(all) let serialNumber: UInt32
access(all) let movieSceneID: UInt32
access(all) let setID: UInt32
access(all) let numMomentsInEdition: UInt32?
init(
title: String?,
sceneDescription: String?,
universe: String?,
studio: String?,
contentType: String?,
characterNames: String?,
actorNames: String?,
directorOrCreator: String?,
releaseYear: String?,
genre: String?,
dropDate: String?,
dateOfMoment: String?,
sceneCategory: String?,
sceneType: String?,
seriesNumber: UInt32?,
setName: String?,
serialNumber: UInt32,
movieSceneID: UInt32,
setID: UInt32,
numMomentsInEdition: UInt32?
) {
self.title = title
self.sceneDescription = sceneDescription
self.universe = universe
self.studio = studio
self.contentType = contentType
self.characterNames = characterNames
self.actorNames = actorNames
self.directorOrCreator = directorOrCreator
self.releaseYear = releaseYear
self.genre = genre
self.dropDate = dropDate
self.dateOfMoment = dateOfMoment
self.sceneCategory = sceneCategory
self.sceneType = sceneType
self.seriesNumber = seriesNumber
self.setName = setName
self.serialNumber = serialNumber
self.movieSceneID = movieSceneID
self.setID = setID
self.numMomentsInEdition = numMomentsInEdition
}
}
// The resource that represents the Moment NFTs
//
access(all) resource NFT: NonFungibleToken.NFT {
// Global unique moment ID
access(all) let id: UInt64
// Struct of Moment metadata
access(all) let data: MomentData
init(serialNumber: UInt32, movieSceneID: UInt32, setID: UInt32, subeditionID: UInt32) {
// Increment the global Moment IDs
Crowdflix.totalSupply = Crowdflix.totalSupply + UInt64(1)
self.id = Crowdflix.totalSupply
// Set the metadata struct
self.data = MomentData(setID: setID, movieSceneID: movieSceneID, serialNumber: serialNumber)
emit MomentMinted(
momentID: self.id,
movieSceneID: movieSceneID,
setID: self.data.setID,
serialNumber: self.data.serialNumber,
subeditionID: subeditionID
)
}
// If the Moment is destroyed, emit an event to indicate
// to outside observers that it has been destroyed
access(all) event ResourceDestroyed(
id: UInt64 = self.id,
serialNumber: UInt32 = self.data.serialNumber,
movieSceneID: UInt32 = self.data.movieSceneID,
setID: UInt32 = self.data.setID
)
access(all) view fun name(): String {
let title: String = Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "Title") ?? ""
let sceneType: String = Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "SceneType") ?? ""
return title
.concat(" ")
.concat(sceneType)
}
// The description of the Moment.
// build the description using set, series, and serial number.
access(all) view fun description(): String {
// Build the description using set name, series number, and serial number
let setName: String = Crowdflix.getSetName(setID: self.data.setID) ?? ""
let serialNumber: String = self.data.serialNumber.toString()
let seriesNumber: String = Crowdflix.getSetSeries(setID: self.data.setID)?.toString() ?? ""
return "A series "
.concat(seriesNumber)
.concat(" ")
.concat(setName)
.concat(" moment with serial number ")
.concat(serialNumber)
}
// All supported metadata views for the Moment including the Core NFT Views
access(all) view fun getViews(): [Type] {
return [
Type<MetadataViews.Display>(),
Type<MovieSceneMomentMetadataView>(),
Type<MetadataViews.Royalties>(),
Type<MetadataViews.Editions>(),
Type<MetadataViews.ExternalURL>(),
Type<MetadataViews.NFTCollectionData>(),
Type<MetadataViews.NFTCollectionDisplay>(),
Type<MetadataViews.Serial>(),
Type<MetadataViews.Traits>(),
Type<MetadataViews.Medias>()
]
}
// resolves the view with the given type for the NFT
access(all) fun resolveView(_ view: Type): AnyStruct? {
switch view {
case Type<MetadataViews.Display>():
return MetadataViews.Display(
name: self.name(),
description: self.description(),
thumbnail: MetadataViews.HTTPFile(url: self.posterimage())
)
// Custom metadata view unique to Crowdflix Moments
case Type<MovieSceneMomentMetadataView>():
return MovieSceneMomentMetadataView(
title: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "Title"),
sceneDescription: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "SceneDescription"),
universe: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "Universe"),
studio: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "Studio"),
contentType: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "ContentType"),
characterNames: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "CharacterNames"),
actorNames: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "ActorNames"),
directorOrCreator: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "DirectorOrCreator"),
releaseYear: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "ReleaseYear"),
genre: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "Genre"),
dropDate: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "DropDate"),
dateOfMoment: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "DateOfMoment"),
sceneCategory: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "SceneCategory"),
sceneType: Crowdflix.getMovieSceneMetaDataByField(movieSceneID: self.data.movieSceneID, field: "SceneType"),
seriesNumber: Crowdflix.getSetSeries(setID: self.data.setID),
setName: Crowdflix.getSetName(setID: self.data.setID),
serialNumber: self.data.serialNumber,
movieSceneID: self.data.movieSceneID,
setID: self.data.setID,
numMomentsInEdition: Crowdflix.getNumMomentsInEdition(setID: self.data.setID, movieSceneID: self.data.movieSceneID)
)
case Type<MetadataViews.Editions>():
let name = self.getEditionName()
let max = Crowdflix.getNumMomentsInEdition(setID: self.data.setID, movieSceneID: self.data.movieSceneID) ?? 0
let editionInfo = MetadataViews.Edition(name: name, number: UInt64(self.data.serialNumber), max: max > 0 ? UInt64(max) : nil)
let editionList: [MetadataViews.Edition] = [editionInfo]
return MetadataViews.Editions(
editionList
)
case Type<MetadataViews.Serial>():
return MetadataViews.Serial(
UInt64(self.data.serialNumber)
)
case Type<MetadataViews.Royalties>():
return Crowdflix.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.Royalties>())
case Type<MetadataViews.ExternalURL>():
return MetadataViews.ExternalURL(self.getMomentURL())
case Type<MetadataViews.NFTCollectionData>():
return Crowdflix.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionData>())
case Type<MetadataViews.NFTCollectionDisplay>():
return Crowdflix.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionDisplay>())
case Type<MetadataViews.Traits>():
return self.resolveTraitsView()
case Type<MetadataViews.Medias>():
return MetadataViews.Medias(
[
MetadataViews.Media(
file: MetadataViews.HTTPFile(
url: self.posterimage()
),
mediaType: "image/jpeg"
),
MetadataViews.Media(
file: MetadataViews.HTTPFile(
url: self.video()
),
mediaType: "video/mp4"
)
]
)
}
return nil
}
// resolves this NFT's Traits view
access(all) fun resolveTraitsView(): MetadataViews.Traits {
// sports radar team id
let excludedNames: [String] = []
// Get subedition
let subedition = Crowdflix.getSubeditionByNFTID(self.id)
// Create a dictionary of this NFT's traits with default metadata
var traits: {String: AnyStruct} = {
"SeriesNumber": Crowdflix.getSetSeries(setID: self.data.setID),
"SetName": Crowdflix.getSetName(setID: self.data.setID),
"SerialNumber": self.data.serialNumber,
"Subedition": subedition?.name ?? "Standard",
"SubeditionID": subedition?.subeditionID ?? 0
}
// Add MovieScene specific data
traits = self.mapMovieSceneData(dict: traits)
return MetadataViews.dictToTraits(dict: traits, excludedNames: excludedNames)
}
// Functions used for computing MetadataViews
// mapMovieSceneData helps build our trait map from MovieScene metadata
// Returns: The trait map with all non-empty fields from MovieScene data added
access(all) fun mapMovieSceneData(dict: {String: AnyStruct}) : {String: AnyStruct} {
let movieSceneMetadata = Crowdflix.getMovieSceneMetaData(movieSceneID: self.data.movieSceneID) ?? {}
for name in movieSceneMetadata.keys {
let value = movieSceneMetadata[name] ?? ""
if value != "" {
dict.insert(key: name, value)
}
}
return dict
}
// getMomentURL
// Returns: The computed external url of the moment
access(all) view fun getMomentURL(): String {
return "https://marketplace.crowdflix.io/moment/".concat(self.id.toString())
}
// getEditionName Moment's edition name is a combination of the Moment's setName and movieSceneID
// `setName: #movieSceneID`
access(all) view fun getEditionName(): String {
let setName: String = Crowdflix.getSetName(setID: self.data.setID) ?? ""
let editionName = setName.concat(": #").concat(self.data.movieSceneID.toString())
return editionName
}
access(all) view fun assetPath(): String {
return "https://assets.crowdflix.io/media/".concat(self.id.toString())
}
// returns a url to display poster image
access(all) view fun posterimage(): String {
return self.appendOptionalParams(url: self.assetPath().concat("/poster"), firstDelim: "?")
}
// a url to display a video associated with the moment
access(all) view fun video(): String {
return self.appendOptionalParams(url: self.assetPath().concat("/video"), firstDelim: "?")
}
// appends and optional network param needed to resolve the media
access(all) view fun appendOptionalParams(url: String, firstDelim: String): String {
if Crowdflix.Network() == "testnet" {
return url.concat(firstDelim).concat("testnet")
}
return url
}
// Create an empty Collection for Crowdflix NFTs and return it to the caller
access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
return <- Crowdflix.createEmptyCollection(nftType: Type<@NFT>())
}
}
// Admin is a special authorization resource that
// allows the owner to perform important functions to modify the
// various aspects of the Plays, Sets, and Moments
//
access(all) resource Admin {
// createMovieScene creates a new MovieScene struct
// and stores it in the MovieScenes dictionary in the Crowdflix smart contract
//
// Parameters: metadata: A dictionary mapping metadata titles to their data
// example: {"Title": "Avengers: Endgame", "sceneDescription": "Iron Man's iconic snap defeating Thanos"}
//
// Returns: the ID of the new MovieScene object
//
access(all) fun createMovieScene(metadata: {String: String}): UInt32 {
// Create the new MovieScene
var newMovieScene = MovieScene(metadata: metadata)
let newID = newMovieScene.movieSceneID
// Increment the ID so that it isn't used again
Crowdflix.nextMovieSceneID = Crowdflix.nextMovieSceneID + UInt32(1)
emit MovieSceneCreated(id: newMovieScene.movieSceneID, metadata: metadata)
// Store it in the contract storage
Crowdflix.movieSceneDatas[newID] = newMovieScene
return newID
}
// createSet creates a new Set resource and stores it
// in the sets mapping in the Crowdflix contract
//
// Parameters: name: The name of the Set
//
// Returns: The ID of the created set
access(all) fun createSet(name: String): UInt32 {
// Create the new Set
var newSet <- create Set(name: name)
// Increment the setID so that it isn't used again
Crowdflix.nextSetID = Crowdflix.nextSetID + UInt32(1)
let newID = newSet.setID
emit SetCreated(setID: newSet.setID, series: Crowdflix.currentSeries)
// Store it in the sets mapping field
Crowdflix.sets[newID] <-! newSet
return newID
}
// borrowSet returns a reference to a set in the Crowdflix
// contract so that the admin can call methods on it
//
// Parameters: setID: The ID of the Set that you want to
// get a reference to
//
// Returns: A reference to the Set with all of the fields
// and methods exposed
//
access(all) view fun borrowSet(setID: UInt32): &Set {
pre {
Crowdflix.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
}
// Get a reference to the Set and return it
// use `&` to indicate the reference to the object and type
return (&Crowdflix.sets[setID] as &Set?)!
}
// startNewSeries ends the current series by incrementing
// the series number, meaning that Moments minted after this
// will use the new series number
//
// Returns: The new series number
//
access(all) fun startNewSeries(): UInt32 {
// End the current series and start a new one
// by incrementing the Crowdflix series number
Crowdflix.currentSeries = Crowdflix.currentSeries + UInt32(1)
emit NewSeriesStarted(newCurrentSeries: Crowdflix.currentSeries)
return Crowdflix.currentSeries
}
// createSubeditionResource creates new SubeditionMap resource that
// will be used to mint Moments with Subeditions
access(all) fun createSubeditionAdminResource() {
Crowdflix.account.storage.save<@SubeditionAdmin>(<- create SubeditionAdmin(), to: Crowdflix.SubeditionAdminStoragePath())
}
// setMomentsSubedition saves which Subedition the Moment belongs to
//
// Parameters: nftID: The ID of the NFT
// subeditionID: The ID of the Subedition the Moment belongs to
// setID: The ID of the Set that the Moment references
// movieSceneID: The ID of the MovieScene that the Moment references
//
access(all) fun setMomentsSubedition(nftID: UInt64, subeditionID: UInt32, setID: UInt32, movieSceneID: UInt32) {
let subeditionAdmin = Crowdflix.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
?? panic("No subedition admin resource in storage")
subeditionAdmin.setMomentsSubedition(nftID: nftID, subeditionID: subeditionID, setID: setID, movieSceneID: movieSceneID)
}
// createSubedition creates a new Subedition struct
// and stores it in the Subeditions dictionary in the SubeditionAdmin resource
//
// Parameters: name: The name of the Subedition
// metadata: A dictionary mapping metadata titles to their data
//
// Returns: the ID of the new Subedition object
//
access(all) fun createSubedition(name: String, metadata: {String: String}): UInt32 {
let subeditionAdmin = Crowdflix.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
?? panic("No subedition admin resource in storage")
return subeditionAdmin.createSubedition(name:name, metadata:metadata)
}
// createNewAdmin creates a new Admin resource
//
access(all) fun createNewAdmin(): @Admin {
return <- create Admin()
}
}
// This is the interface that users can cast their Moment Collection as
// to allow others to deposit Moments into their Collection. It also allows for reading
// the IDs of Moments in the Collection.
/// Deprecated: This is no longer used for defining access control anymore.
access(all) resource interface MomentCollectionPublic : NonFungibleToken.CollectionPublic {
access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection})
access(all) fun borrowMoment(id: UInt64): &NFT? {
// If the result isn't nil, the id of the returned reference
// should be the same as the argument to the function
post {
(result == nil) || (result?.id == id):
"Cannot borrow Moment reference: The ID of the returned reference is incorrect"
}
}
}
// Collection is a resource that every user who owns NFTs
// will store in their account to manage their NFTS
//
access(all) resource Collection: MomentCollectionPublic, NonFungibleToken.Collection {
// Dictionary of Moment conforming tokens
// NFT is a resource type with a UInt64 ID field
access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
init() {
self.ownedNFTs <- {}
}
// Return a list of NFT types that this receiver accepts
access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
let supportedTypes: {Type: Bool} = {}
supportedTypes[Type<@NFT>()] = true
return supportedTypes
}
// Return whether or not the given type is accepted by the collection
// A collection that can accept any type should just return true by default
access(all) view fun isSupportedNFTType(type: Type): Bool {
if type == Type<@NFT>() {
return true
}
return false
}
// Return the amount of NFTs stored in the collection
access(all) view fun getLength(): Int {
return self.ownedNFTs.length
}
// Create an empty Collection for Crowdflix NFTs and return it to the caller
access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
return <- Crowdflix.createEmptyCollection(nftType: Type<@NFT>())
}
// withdraw removes an Moment from the Collection and moves it to the caller
//
// Parameters: withdrawID: The ID of the NFT
// that is to be removed from the Collection
//
// returns: @NonFungibleToken.NFT the token that was withdrawn
access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
// Borrow nft and check if locked
let nft = self.borrowNFT(withdrawID)
?? panic("Cannot borrow: empty reference")
// Remove the nft from the Collection
let token <- self.ownedNFTs.remove(key: withdrawID)
?? panic("Cannot withdraw: Moment does not exist in the collection")
emit Withdraw(id: token.id, from: self.owner?.address)
// Return the withdrawn token
return <- token
}
// batchWithdraw withdraws multiple tokens and returns them as a Collection
//
// Parameters: ids: An array of IDs to withdraw
//
// Returns: @NonFungibleToken.Collection: A collection that contains
// the withdrawn moments
//
access(NonFungibleToken.Withdraw) fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} {
// Create a new empty Collection
var batchCollection <- create Collection()
// Iterate through the ids and withdraw them from the Collection
for id in ids {
batchCollection.deposit(token: <- self.withdraw(withdrawID: id))
}
// Return the withdrawn tokens
return <- batchCollection
}
// deposit takes a Moment and adds it to the Collections dictionary
//
// Paramters: token: the NFT to be deposited in the collection
//
access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
// Cast the deposited token as a Crowdflix NFT to make sure
// it is the correct type
let token <- token as! @NFT
// Get the token's ID
let id = token.id
// Add the new token to the dictionary
let oldToken <- self.ownedNFTs[id] <- token
// Only emit a deposit event if the Collection
// is in an account's storage
if self.owner?.address != nil {
emit Deposit(id: id, to: self.owner?.address)
}
// Destroy the empty old token that was "removed"
destroy oldToken
}
// batchDeposit takes a Collection object as an argument
// and deposits each contained NFT into this Collection
access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) {
// Get an array of the IDs to be deposited
let keys = tokens.getIDs()
// Iterate through the keys in the collection and deposit each one
for key in keys {
self.deposit(token: <- tokens.withdraw(withdrawID: key))
}
// Destroy the empty Collection
destroy tokens
}
// destroyMoments destroys moments in this collection
// unlocks the moments if they are locked
//
// Parameters: ids: An array of NFT IDs
// to be destroyed from the Collection
access(NonFungibleToken.Update) fun destroyMoments(ids: [UInt64]) {
for id in ids {
// Remove the nft from the Collection
let token <- self.ownedNFTs.remove(key: id)
?? panic("Cannot destroy: Moment does not exist in collection: ".concat(id.toString()))
// Emit a withdraw event here so that platforms do not have to understand Crowdflix-specific events to see ownership change
// A withdraw without a corresponding deposit means the NFT in question has no owner address
emit Withdraw(id: id, from: self.owner?.address)
destroy token
}
}
// getIDs returns an array of the IDs that are in the Collection
access(all) view fun getIDs(): [UInt64] {
return self.ownedNFTs.keys
}
// borrowNFT Returns a borrowed reference to a Moment in the Collection
// so that the caller can read its ID
//
// Parameters: id: The ID of the NFT to get the reference for
//
// Returns: A reference to the NFT
//
// Note: This only allows the caller to read the ID of the NFT,
// not any crowdflix specific data. Please use borrowMoment to
// read Moment data.
//
access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
return &self.ownedNFTs[id]
}
// borrowMoment returns a borrowed reference to a Moment
// so that the caller can read data and call methods from it.
// They can use this to read its setID, movieSceneID, serialNumber,
// or any of the setData or MovieScene data associated with it by
// getting the setID or movieSceneID and reading those fields from
// the smart contract.
//
// Parameters: id: The ID of the NFT to get the reference for
//
// Returns: A reference to the NFT
access(all) view fun borrowMoment(id: UInt64): &NFT? {
return self.borrowNFT(id) as! &NFT?
}
access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
return nft as &{ViewResolver.Resolver}
}
return nil
}
}
// -----------------------------------------------------------------------
// Crowdflix contract-level function definitions
// -----------------------------------------------------------------------
// createEmptyCollection creates a new, empty Collection object so that
// a user can store it in their account storage.
// Once they have a Collection in their storage, they are able to receive
// Moments in transactions.
//
access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
if nftType != Type<@NFT>() {
panic("NFT type is not supported")
}
return <- create Crowdflix.Collection()
}
// getAllMovieScenes returns all the movieScenes in crowdflix
//
// Returns: An array of all the movieScenes that have been created
access(all) view fun getAllMovieScenes(): [MovieScene] {
return Crowdflix.movieSceneDatas.values
}
// getMovieSceneMetaData returns all the metadata associated with a specific MovieScene
//
// Parameters: movieSceneID: The id of the MovieScene that is being searched
//
// Returns: The metadata as a String to String mapping optional
access(all) view fun getMovieSceneMetaData(movieSceneID: UInt32): {String: String}? {
return self.movieSceneDatas[movieSceneID]?.metadata
}
// getMovieSceneMetaDataByField returns the metadata associated with a
// specific field of the metadata
// Ex: field: "contentType" will return something
// like "movie" or "tv show"
//
// Parameters: movieSceneID: The id of the MovieScene that is being searched
// field: The field to search for
//
// Returns: The metadata field as a String Optional
access(all) view fun getMovieSceneMetaDataByField(movieSceneID: UInt32, field: String): String? {
// Don't force a revert if the movieSceneID or field is invalid
if let movieScene = Crowdflix.movieSceneDatas[movieSceneID] {
return movieScene.metadata[field]
}
return nil
}
// getSetData returns the data that the specified Set
// is associated with.
//
// Parameters: setID: The id of the Set that is being searched
//
// Returns: The QuerySetData struct that has all the important information about the set
access(all) fun getSetData(setID: UInt32): QuerySetData? {
if Crowdflix.sets[setID] == nil {
return nil
}
return QuerySetData(setID: setID)
}
// getSetName returns the name that the specified Set
// is associated with.
//
// Parameters: setID: The id of the Set that is being searched
//
// Returns: The name of the Set
access(all) view fun getSetName(setID: UInt32): String? {
// Don't force a revert if the setID is invalid
return Crowdflix.setDatas[setID]?.name
}
// getSetSeries returns the series that the specified Set
// is associated with.
//
// Parameters: setID: The id of the Set that is being searched
//
// Returns: The series that the Set belongs to
access(all) view fun getSetSeries(setID: UInt32): UInt32? {
// Don't force a revert if the setID is invalid
return Crowdflix.setDatas[setID]?.series
}
// getSetIDsByName returns the IDs that the specified Set name
// is associated with.
//
// Parameters: setName: The name of the Set that is being searched
//
// Returns: An array of the IDs of the Set if it exists, or nil if doesn't
access(all) fun getSetIDsByName(setName: String): [UInt32]? {
var setIDs: [UInt32] = []
// Iterate through all the setDatas and search for the name
for setData in Crowdflix.setDatas.values {
if setName == setData.name {
// If the name is found, return the ID
setIDs.append(setData.setID)
}
}
// If the name isn't found, return nil
// Don't force a revert if the setName is invalid
if setIDs.length == 0 {
return nil
}
return setIDs
}
// getMovieScenesInSet returns the list of MovieScene IDs that are in the Set
//
// Parameters: setID: The id of the Set that is being searched
//
// Returns: An array of MovieScene IDs
access(all) view fun getMovieScenesInSet(setID: UInt32): [UInt32]? {
// Don't force a revert if the setID is invalid
return Crowdflix.sets[setID]?.movieScenes
}
// isEditionRetired returns a boolean that indicates if a Set/MovieScene combo
// (otherwise known as an edition) is retired.
// If an edition is retired, it still remains in the Set,
// but Moments can no longer be minted from it.
//
// Parameters: setID: The id of the Set that is being searched
// movieSceneID: The id of the MovieScene that is being searched
//
// Returns: Boolean indicating if the edition is retired or not
access(all) fun isEditionRetired(setID: UInt32, movieSceneID: UInt32): Bool? {
// Return the retired status for the MovieScene in the set if it exists
if let setdata = self.getSetData(setID: setID) {
return setdata.getRetired()[movieSceneID]
}
return nil
}
// isSetLocked returns a boolean that indicates if a Set
// is locked. If it's locked,
// new MovieScenes can no longer be added to it,
// but Moments can still be minted from MovieScenes the set contains.
//
// Parameters: setID: The id of the Set that is being searched
//
// Returns: Boolean indicating if the Set is locked or not
access(all) view fun isSetLocked(setID: UInt32): Bool? {
// Don't force a revert if the setID is invalid
return Crowdflix.sets[setID]?.locked
}
// getNumMomentsInEdition return the number of Moments that have been
// minted from a certain edition.
//
// Parameters: setID: The id of the Set that is being searched
// movieSceneID: The id of the MovieScene that is being searched
//
// Returns: The total number of Moments
// that have been minted from an edition
access(all) fun getNumMomentsInEdition(setID: UInt32, movieSceneID: UInt32): UInt32? {
// Return the number of moments minted for the MovieScene in the set if it exists
if let setdata = self.getSetData(setID: setID) {
return setdata.getNumberMintedPerMovieScene()[movieSceneID]
}
return nil
}
// getMomentsSubedition returns the Subedition the Moment belongs to
//
// Parameters: nftID: The ID of the NFT
//
// returns: UInt32? Subedition's ID if exists
//
access(all) view fun getMomentsSubedition(nftID: UInt64): UInt32? {
let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
?? panic("No subedition admin resource in storage")
return subeditionAdmin.getMomentsSubedition(nftID: nftID)
}
// getAllSubeditions returns all the subeditions in crowdflix subeditionAdmin resource
//
// Returns: An array of all the subeditions that have been created
access(all) view fun getAllSubeditions(): &[Subedition] {
let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
?? panic("No subedition admin resource in storage")
return subeditionAdmin.subeditionDatas.values
}
// getSubeditionByID returns the subedition struct entity
//
// Parameters: subeditionID: The id of the Subedition that is being searched
//
// Returns: The Subedition struct
access(all) view fun getSubeditionByID(subeditionID: UInt32): &Subedition {
let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
?? panic("No subedition admin resource in storage")
return subeditionAdmin.subeditionDatas[subeditionID]!
}
// getSubeditionByNFTID returns the subedition struct that the NFT belongs to
//
// Parameters: nftID: The id of the NFT that is being searched
//
// Returns: The subedition struct that the NFT belongs to
access(all) view fun getSubeditionByNFTID(_ nftID: UInt64): &Subedition? {
if let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath()) {
if let subeditionID = subeditionAdmin.getMomentsSubedition(nftID: nftID) {
return subeditionAdmin.subeditionDatas[subeditionID]
}
}
return nil
}
// This script reads the public nextSubeditionID from the SubeditionAdmin resource and
// returns that number to the caller
//
// Returns: UInt32
// the next number in nextSubeditionID from the SubeditionAdmin resource
access(all) view fun getNextSubeditionID(): UInt32 {
let subeditionAdmin = self.account.storage.borrow<&SubeditionAdmin>(from: Crowdflix.SubeditionAdminStoragePath())
?? panic("No subedition admin resource in storage")
return subeditionAdmin.nextSubeditionID
}
// SubeditionAdmin is a resource that allows Set to mint Moments with Subeditions
//
access(all) struct Subedition {
access(all) let subeditionID: UInt32
access(all) let name: String
access(all) let metadata: {String: String}
init(subeditionID: UInt32, name: String, metadata: {String: String}) {
pre {
name.length != 0: "New Subedition name cannot be empty"
}
self.subeditionID = subeditionID
self.name = name
self.metadata = metadata
}
}
access(all) resource SubeditionAdmin {
// Map of number of already minted Moments using Subedition.
// When a new Moment with Subedition is minted, 1 is added to the
// number in this map by the key, formed by concatinating of
// SetID, MovieSceneID and SubeditionID
access(contract) let numberMintedPerSubedition: {String: UInt32}
// Map of Subedition which the Moment belongs to.
// This map updates after each minting.
access(contract) let momentsSubedition: {UInt64: UInt32}
// The ID that is used to create Subeditions.
// Every time a Subeditions is created, subeditionID is assigned
// to the new Subedition's ID and then is incremented by 1.
access(contract) var nextSubeditionID: UInt32
// Variable size dictionary of Subedition structs
access(contract) let subeditionDatas: {UInt32: Subedition}
// createSubedition creates a new Subedition struct
// and stores it in the Subeditions dictionary in the SubeditionAdmin resource
//
// Parameters: name: The name of the Subedition
// metadata: A dictionary mapping metadata titles to their data
//
// Returns: the ID of the new Subedition object
//
access(all) fun createSubedition(name: String, metadata: {String: String}): UInt32 {
let newID = self.nextSubeditionID
var newSubedition = Subedition(subeditionID: newID, name: name, metadata: metadata)
self.nextSubeditionID = self.nextSubeditionID + UInt32(1)
self.subeditionDatas[newID] = newSubedition
emit SubeditionCreated(subeditionID: newID, name: name, metadata: metadata)
return newID
}
// getMomentsSubedition function that return's wich Subedition the Moment belongs to
//
// Parameters: nftID: The ID of the NFT
//
// returns: UInt32? Subedition's ID if exists
//
access(all) view fun getMomentsSubedition(nftID: UInt64): UInt32? {
return self.momentsSubedition[nftID]
}
// getNumberMintedPerSubedition function that return's
// the number of Moments that have been minted for this subedition
// to use as this Moment's serial number
//
// Parameters: setID: The ID of the Set Moment will be minted from
// movieSceneID: The ID of the MovieScene Moment will be minted from
// subeditionID: The ID of the Subedition using which moment will be minted
//
// returns: UInt32 Number of Moments, already minted for this Subedition
//
access(all) fun getNumberMintedPerSubedition(setID: UInt32, movieSceneID: UInt32, subeditionID: UInt32): UInt32 {
let setPlaySubedition = self.getSetPlaySubeditionString(setID, movieSceneID, subeditionID)
if !self.numberMintedPerSubedition.containsKey(setPlaySubedition) {
self.numberMintedPerSubedition.insert(key: setPlaySubedition, UInt32(0))
return UInt32(0)
}
return self.numberMintedPerSubedition[setPlaySubedition]!
}
// addToNumberMintedPerSubedition function that increments 1 to the
// number of Moments that have been minted for this subedition
//
// Parameters: setID: The ID of the Set Moment will be minted from
// movieSceneID: The ID of the MovieScene Moment will be minted from
// subeditionID: The ID of the Subedition using which moment will be minted
//
//
access(contract) fun addToNumberMintedPerSubedition(setID: UInt32, movieSceneID: UInt32, subeditionID: UInt32) {
let setPlaySubedition = self.getSetPlaySubeditionString(setID, movieSceneID, subeditionID)
// Get number of moments minted for this subedition
let numberMinted = self.numberMintedPerSubedition[setPlaySubedition]
?? panic("Could not find number of moments minted for specified Subedition!")
// Increment the number of moments minted for this subedition
self.numberMintedPerSubedition[setPlaySubedition] = numberMinted + UInt32(1)
}
// getSetPlaySubeditionString builds a string that is used as a key in the numberMintedPerSubedition map
access(self) view fun getSetPlaySubeditionString(_ setID: UInt32, _ movieSceneID: UInt32, _ subeditionID: UInt32): String {
return setID.toString().concat(movieSceneID.toString()).concat(subeditionID.toString())
}
// setMomentsSubedition saves which Subedition the Moment belongs to
//
// Parameters: nftID: The ID of the NFT
// subeditionID: The ID of the Subedition the Moment belongs to
// setID: The ID of the Set that the Moment references
// movieSceneID: The ID of the MovieScene that the Moment references
//
access(all) fun setMomentsSubedition(nftID: UInt64, subeditionID: UInt32, setID: UInt32, movieSceneID: UInt32) {
pre {
!self.momentsSubedition.containsKey(nftID) : "Subedition for this moment already exists!"
}
self.momentsSubedition.insert(key: nftID, subeditionID)
emit SubeditionAddedToMoment(momentID: nftID, subeditionID: subeditionID, setID: setID, movieSceneID: movieSceneID)
}
init() {
self.momentsSubedition = {}
self.numberMintedPerSubedition = {}
self.subeditionDatas = {}
self.nextSubeditionID = 1
}
}
//------------------------------------------------------------
// Contract MetadataViews
//------------------------------------------------------------
// getContractViews returns the metadata view types available for this contract
access(all) view fun getContractViews(resourceType: Type?): [Type] {
return [
Type<MetadataViews.NFTCollectionData>(),
Type<MetadataViews.NFTCollectionDisplay>(),
Type<MetadataViews.Royalties>()
]
}
// resolveContractView resolves this contract's metadata views
access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
post {
result == nil || result!.getType() == viewType: "The returned view must be of the given type or nil"
}
switch viewType {
case Type<MetadataViews.NFTCollectionData>():
return MetadataViews.NFTCollectionData(
storagePath: /storage/MomentCollection,
publicPath: /public/MomentCollection,
publicCollection: Type<&Collection>(),
publicLinkedType: Type<&Collection>(),
createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
return <- Crowdflix.createEmptyCollection(nftType: Type<@NFT>())
})
)
case Type<MetadataViews.NFTCollectionDisplay>():
let bannerImage = MetadataViews.Media(
file: MetadataViews.HTTPFile(
url: "https://marketplace.crowdflix.io/images/crowdflix-logo-horizontal-white.svg"
),
mediaType: "image/svg+xml"
)
let squareImage = MetadataViews.Media(
file: MetadataViews.HTTPFile(
url: "https://marketplace.crowdflix.io/images/favicon.svg"
),
mediaType: "image/svg+xml"
)
return MetadataViews.NFTCollectionDisplay(
name: "Crowdflix",
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.",
externalURL: MetadataViews.ExternalURL("https://marketplace.crowdflix.io"),
squareImage: squareImage,
bannerImage: bannerImage,
socials: {
"twitter": MetadataViews.ExternalURL("https://twitter.com/crowdflix")
}
)
case Type<MetadataViews.Royalties>():
let royaltyReceiver: Capability<&{FungibleToken.Receiver}> =
getAccount(Crowdflix.RoyaltyAddress()).capabilities.get<&{FungibleToken.Receiver}>(MetadataViews.getRoyaltyReceiverPublicPath())!
return MetadataViews.Royalties(
[
MetadataViews.Royalty(
receiver: royaltyReceiver,
cut: 0.05,
description: "Crowdflix marketplace royalty"
)
]
)
}
return nil
}
// -----------------------------------------------------------------------
// Crowdflix initialization function
// -----------------------------------------------------------------------
//
init() {
// Initialize contract fields
self.currentSeries = 0
self.movieSceneDatas = {}
self.setDatas = {}
self.sets <- {}
self.nextMovieSceneID = 1
self.nextSetID = 1
self.totalSupply = 0
// Put a new Collection in storage
self.account.storage.save<@Collection>(<- create Collection(), to: /storage/MomentCollection)
// Create and publish a capability for the collection
self.account.capabilities.publish(
self.account.capabilities.storage.issue<&Collection>(/storage/MomentCollection),
at: /public/MomentCollection
)
// Put the Minter in storage
self.account.storage.save<@Admin>(<- create Admin(), to: /storage/CrowdflixAdmin)
}
}
Cadence Script
1transaction(name: String, code: String ) {
2 prepare(signer: auth(AddContract) &Account) {
3 signer.contracts.add(name: name, code: code.utf8 )
4 }
5 }