Smart Contract
CricketMoments
A.f827352428f8f46b.CricketMoments
1// SPDX-License-Identifier: UNLICENSED
2
3import NonFungibleToken from 0x631e88ae7f1d7c20
4
5/**
6
7# CricketMoments
8
9The main contract managing the cricket moments/NFTs created by Faze.
10
11## `NFT` Resource
12
13Each NFT created using this contract consists of -
14- id : globally unique identifier for the NFT
15- momentId : The moment of which the NFT is a copy
16- serial : serial number of this NFT
17- metadata : Extra metadata for the NFT. It contains a description and an IPFS url, which
18contains the link to the video/image.
19
20
21## `Collection` resource
22
23Each account that owns cricket moments would need to have an instance
24of the Collection resource stored in their account storage.
25
26The Collection resource has methods that the owner and other users can call.
27
28## `CricketMomentsCollectionPublic` resource interfaces
29
30An Interface which is implemented by the collection resource. It contains
31functions for depositing moments, borrowing moments and getting id's of all
32 the moments stored in the collection.
33
34## Locking Moments
35The NFTMinter resource can lock specific moments. After locking a particular moment, no more copies
36of that moment can be minted. Whether a moment is locked or not can also be read easily
37using isMomentLocked function.
38
39*/
40access(all) contract CricketMoments: NonFungibleToken {
41
42 // Events
43 access(all) event ContractInitialized()
44 access(all) event Withdraw(id: UInt64, from: Address?)
45 access(all) event Deposit(id: UInt64, to: Address?)
46 access(all) event Minted(id: UInt64, momentId:UInt64, serial:UInt64, ipfs:String)
47
48 // Named Paths
49 access(all) let CollectionStoragePath: StoragePath
50 access(all) let CollectionPublicPath: PublicPath
51 access(all) let MinterStoragePath: StoragePath
52
53 // totalSupply, the total number of CricketMoments that have been minted
54 access(all) var totalSupply: UInt64
55
56 // The total number of unique moments that have been minted
57 access(all) var totalMomentIds: UInt64
58
59 // A dictionary to track the moments that have been locked. No more copies of a locked moment can be minted
60 access(self) var locked: {UInt64:Bool}
61
62 // A dictionary to store the next serial number to be minted for a particular moment Id.
63 access(self) var nextSerial: {UInt64:UInt64}
64
65 access(all) view fun getContractViews(resourceType: Type?): [Type] {
66 return []
67 }
68
69 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
70 return nil
71 }
72
73 // NFT
74 // A Moment as an NFT
75 access(all) resource NFT: NonFungibleToken.NFT {
76 // Token's ID
77 access(all) let id: UInt64
78
79 // Token's momentId to identify the moment
80 access(all) let momentId: UInt64
81
82 // Token's serial number
83 access(all) let serial: UInt64
84
85 // Token's metadata as a string dictionary
86 access(self) let metadata: {String : String}
87
88 // initializer
89 init(id: UInt64, momentId: UInt64, serial: UInt64, metadata: {String : String}) {
90 self.id = id
91 self.momentId = momentId
92 self.serial = serial
93 self.metadata = metadata
94 }
95
96 // get complete metadata
97 access(all) fun getMetadata() : {String:String} {
98 return self.metadata;
99 }
100
101 // get metadata field by key
102 access(all) fun getMetadataField(key:String) : String? {
103 if let value = self.metadata[key] {
104 return value
105 }
106 return nil;
107 }
108
109 /// Same as getViews above, but on a specific NFT instead of a contract
110 access(all) view fun getViews(): [Type] {
111 return []
112 }
113
114 /// Same as resolveView above, but on a specific NFT instead of a contract
115 access(all) fun resolveView(_ view: Type): AnyStruct? {
116 return nil
117 }
118
119
120 /// createEmptyCollection creates an empty Collection
121 /// and returns it to the caller so that they can own NFTs
122 /// @{NonFungibleToken.Collection}
123 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
124 return <-CricketMoments.createEmptyCollection(nftType: Type<@CricketMoments.NFT>())
125 }
126 }
127
128 // This is the interface that users can cast their CricketMoments Collection as
129 // to allow others to deposit CricketMoments into their Collection. It also allows for reading
130 // the details of CricketMoments in the Collection.
131 access(all) resource interface CricketMomentsCollectionPublic {
132 access(all) fun deposit(token: @{NonFungibleToken.NFT})
133 access(all) view fun getIDs(): [UInt64]
134 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
135 access(all) view fun borrowCricketMoment(id: UInt64): &CricketMoments.NFT? {
136 // If the result isn't nil, the id of the returned reference
137 // should be the same as the argument to the function
138 post {
139 (result == nil) || (result?.id == id):
140 "Cannot borrow Moment reference: The Id of the returned reference is incorrect"
141 }
142 }
143 }
144
145 // Collection
146 // A collection of Moment NFTs owned by an account
147 //
148 access(all) resource Collection: NonFungibleToken.Collection, CricketMomentsCollectionPublic {
149 // dictionary of NFT conforming tokens
150 // NFT is a resource type with an `UInt64` ID field
151 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
152
153 /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
154 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
155 let supportedTypes: {Type: Bool} = {}
156 supportedTypes[Type<@CricketMoments.NFT>()] = true
157 return supportedTypes
158 }
159
160 /// Returns whether or not the given type is accepted by the collection
161 /// A collection that can accept any type should just return true by default
162 access(all) view fun isSupportedNFTType(type: Type): Bool {
163 return type == Type<@CricketMoments.NFT>()
164 }
165
166 // withdraw
167 // Removes an NFT from the collection and moves it to the caller
168 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
169 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
170
171 emit Withdraw(id: token.id, from: self.owner?.address)
172
173 return <-token
174 }
175
176 // deposit
177 // Takes a NFT and adds it to the collections dictionary
178 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
179 let token <- token as! @CricketMoments.NFT
180
181 let id: UInt64 = token.id
182
183 // add the new token to the dictionary which removes the old one
184 let oldToken <- self.ownedNFTs[id] <- token
185
186 emit Deposit(id: id, to: self.owner?.address)
187
188 destroy oldToken
189 }
190
191 // getIDs
192 // Returns an array of the IDs that are in the collection
193 //
194 access(all) view fun getIDs(): [UInt64] {
195 return self.ownedNFTs.keys
196 }
197
198 // borrowNFT
199 // Gets a reference to an NFT in the collection
200 // so that the caller can read its id
201 //
202 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
203 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
204 }
205
206 // borrowCricketMoment
207 // Gets a reference to an NFT in the collection as a Moment,
208 // exposing all of its fields (including the momentId, serial, and metadata).
209 // This is safe as there are no functions that can be called on the Moment.
210 // Metadata is also a private field, therefore can't be changed using borrowed object.
211 //
212 access(all) view fun borrowCricketMoment(id: UInt64): &CricketMoments.NFT? {
213 if self.ownedNFTs[id] != nil {
214 let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
215 return ref as! &CricketMoments.NFT
216 } else {
217 return nil
218 }
219 }
220
221 /// createEmptyCollection creates an empty Collection of the same type
222 /// and returns it to the caller
223 /// @return A an empty collection of the same type
224 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
225 return <-CricketMoments.createEmptyCollection(nftType: Type<@CricketMoments.NFT>())
226 }
227
228 // initializer
229 //
230 init () {
231 self.ownedNFTs <- {}
232 }
233 }
234
235 /// createEmptyCollection creates an empty Collection for the specified NFT type
236 /// and returns it to the caller so that they can own NFTs
237 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
238 return <- create Collection()
239 }
240
241 // NFTMinter
242 // Resource that an admin or something similar would own to be
243 // able to mint new NFTs
244 //
245 access(all) resource NFTMinter {
246
247 // mintNFTs
248 // Mints multiple new NFTs with the same momentId
249 // Increments serial number
250 // deposits all in the recipients collection using their collection reference
251 //
252 access(all) fun mintNewNFTs(recipient: &{NonFungibleToken.CollectionPublic}, serialQuantity: UInt64, metadata: {String : String}) {
253
254
255 var serialNumber = 1 as UInt64
256 var ipfs: String = metadata["ipfs"] ?? panic("IPFS url not available")
257 while serialNumber <= serialQuantity {
258 emit Minted(
259 id: CricketMoments.totalSupply,
260 momentId:CricketMoments.totalMomentIds,
261 serial:serialNumber,
262 ipfs:ipfs
263 )
264
265 // create NFT and deposit it in the recipient's account using their reference
266 recipient.deposit(token: <-create CricketMoments.NFT(
267 id: CricketMoments.totalSupply,
268 momentId: CricketMoments.totalMomentIds,
269 serial: serialNumber,
270 metadata: metadata
271 ))
272
273 serialNumber = serialNumber + (1 as UInt64)
274
275 CricketMoments.totalSupply = CricketMoments.totalSupply + (1 as UInt64)
276 }
277 // Save current serial number so that next copies of the same moment can start from here
278 CricketMoments.nextSerial[CricketMoments.totalMomentIds] = (serialNumber as UInt64)
279 // Initialize locked as false for a new Moment.
280 CricketMoments.locked[CricketMoments.totalMomentIds] = false
281 // Increment totalMomentIds
282 CricketMoments.totalMomentIds = CricketMoments.totalMomentIds + (1 as UInt64)
283 }
284
285 // Mint more NFTs for a particular momentId that already exists
286 access(all) fun mintOldNFTs(recipient: &{NonFungibleToken.CollectionPublic}, momentId:UInt64, serialQuantity: UInt64, metadata: {String : String}) {
287
288 var ipfs: String = metadata["ipfs"] ?? panic("IPFS url not available")
289 var serialNumber = CricketMoments.nextSerial[momentId] ?? panic("momentId not present")
290 var isLocked = CricketMoments.locked[momentId] ?? panic("momentId not present")
291 if (isLocked==true){
292 panic("Moment already locked. Can't mint any more NFTs for this momentId")
293 }
294
295 var i = 1 as UInt64
296 while i <= serialQuantity {
297 emit Minted(
298 id: CricketMoments.totalSupply,
299 momentId:momentId,
300 serial:serialNumber,
301 ipfs:ipfs
302 )
303
304 // deposit it in the recipient's account using their reference
305 recipient.deposit(token: <-create CricketMoments.NFT(
306 id: CricketMoments.totalSupply,
307 momentId: momentId,
308 serial: serialNumber,
309 metadata: metadata
310 ))
311
312 serialNumber = serialNumber + (1 as UInt64)
313 i = i + 1 as UInt64
314 CricketMoments.totalSupply = CricketMoments.totalSupply + (1 as UInt64)
315 }
316 // Save current serial number so that next copies of the same moment can start from here
317 // No need to increment totalMomentIds
318 CricketMoments.nextSerial[momentId] = serialNumber
319
320 }
321
322 // Lock a particular momentId, so that no more moments with this momentId can be minted.
323 access(all) fun lockMoment(momentId:UInt64) {
324
325 if (CricketMoments.locked[momentId]==nil) {
326 panic("Moment not minted yet")
327 }
328 CricketMoments.locked[momentId]=true
329 }
330 }
331
332 // fetch
333 // Get a reference to a CricketMoments from an account's Collection, if available.
334 // If an account does not have a CricketMoments.Collection, panic.
335 // If it has a collection but does not contain the itemID, return nil.
336 // If it has a collection and that collection contains the itemID, return a reference to that.
337 //
338 access(all) fun fetch(_ from: Address, id: UInt64): &CricketMoments.NFT? {
339 let collection = getAccount(from).capabilities
340 .borrow<&CricketMoments.Collection>(CricketMoments.CollectionPublicPath)
341 ?? panic("Couldn't get collection")
342 // We trust CricketMoments.Collection.borrowMoment to get the correct itemID
343 // (it checks it before returning it).
344 return collection.borrowCricketMoment(id: id)
345 }
346
347 // get next serial for a momentId (nextSerial -1 indicates number of copies minted for this momentId)
348 access(all) fun getNextSerial(momentId:UInt64): UInt64? {
349
350 if let nextSerial = self.nextSerial[momentId] {
351 return nextSerial
352 }
353 return nil
354 }
355
356 // check if a moment is locked. No more copies of a locked moment can be minted
357 access(all) fun isMomentLocked(momentId:UInt64): Bool? {
358
359 if let isLocked = self.locked[momentId] {
360 return isLocked
361 }
362 return nil
363 }
364
365 // initializer
366 //
367 init() {
368 // Set our named paths
369 self.CollectionStoragePath = /storage/CricketMomentsCollection
370 self.CollectionPublicPath = /public/CricketMomentsCollection
371 self.MinterStoragePath = /storage/CricketMomentsMinter
372
373 // Initialize the total supply
374 self.totalSupply = 0
375
376 // Initialize the total Moment IDs
377 self.totalMomentIds = 0
378
379 // Initialize locked and nextSerial as empty dictionaries
380 self.locked = {}
381 self.nextSerial = {}
382
383 // Create a Minter resource and save it to storage
384 let minter <- create NFTMinter()
385 self.account.storage.save(<-minter, to: self.MinterStoragePath)
386
387 emit ContractInitialized()
388 }
389}
390