Smart Contract
CricketMomentsPack
A.4eded0de73020ca5.CricketMomentsPack
1// SPDX-License-Identifier: UNLICENSED
2
3import NonFungibleToken from 0x1d7e57aa55817448
4
5/**
6
7# CricketMomentsPack
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 CricketMomentsPack: 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 CricketMomentsPack 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 <-CricketMomentsPack.createEmptyCollection(nftType: Type<@CricketMomentsPack.NFT>())
125 }
126 }
127
128 // This is the interface that users can cast their CricketMomentsPack Collection as
129 // to allow others to deposit CricketMomentsPack into their Collection. It also allows for reading
130 // the details of CricketMomentsPack in the Collection.
131 access(all) resource interface CricketMomentsPackCollectionPublic {
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): &CricketMomentsPack.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, CricketMomentsPackCollectionPublic {
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<@CricketMomentsPack.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<@CricketMomentsPack.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! @CricketMomentsPack.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 access(all) view fun getIDs(): [UInt64] {
194 return self.ownedNFTs.keys
195 }
196
197 // borrowNFT
198 // Gets a reference to an NFT in the collection
199 // so that the caller can read its id
200 //
201 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
202 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
203 }
204
205 // borrowCricketMoment
206 // Gets a reference to an NFT in the collection as a Moment,
207 // exposing all of its fields (including the momentId, serial, and metadata).
208 // This is safe as there are no functions that can be called on the Moment.
209 // Metadata is also a private field, therefore can't be changed using borrowed object.
210 //
211 access(all) view fun borrowCricketMoment(id: UInt64): &CricketMomentsPack.NFT? {
212 if self.ownedNFTs[id] != nil {
213 let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
214 return ref as! &CricketMomentsPack.NFT
215 } else {
216 return nil
217 }
218 }
219
220 /// createEmptyCollection creates an empty Collection of the same type
221 /// and returns it to the caller
222 /// @return A an empty collection of the same type
223 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
224 return <-CricketMomentsPack.createEmptyCollection(nftType: Type<@CricketMomentsPack.NFT>())
225 }
226
227 // initializer
228 //
229 init () {
230 self.ownedNFTs <- {}
231 }
232 }
233
234 /// createEmptyCollection creates an empty Collection for the specified NFT type
235 /// and returns it to the caller so that they can own NFTs
236 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
237 return <- create Collection()
238 }
239
240 // NFTMinter
241 // Resource that an admin or something similar would own to be
242 // able to mint new NFTs
243 //
244 access(all) resource NFTMinter {
245
246 // mintNFTs
247 // Mints multiple new NFTs with the same momentId
248 // Increments serial number
249 // deposits all in the recipients collection using their collection reference
250 //
251 access(all) fun mintNewNFTs(recipient: &{NonFungibleToken.CollectionPublic}, serialQuantity: UInt64, metadata: {String : String}) {
252
253
254 var serialNumber = 1 as UInt64
255 var ipfs: String = metadata["ipfs"] ?? panic("IPFS url not available")
256 while serialNumber <= serialQuantity {
257 emit Minted(
258 id: CricketMomentsPack.totalSupply,
259 momentId:CricketMomentsPack.totalMomentIds,
260 serial:serialNumber,
261 ipfs:ipfs
262 )
263
264 // create NFT and deposit it in the recipient's account using their reference
265 recipient.deposit(token: <-create CricketMomentsPack.NFT(
266 id: CricketMomentsPack.totalSupply,
267 momentId: CricketMomentsPack.totalMomentIds,
268 serial: serialNumber,
269 metadata: metadata
270 ))
271
272 serialNumber = serialNumber + (1 as UInt64)
273
274 CricketMomentsPack.totalSupply = CricketMomentsPack.totalSupply + (1 as UInt64)
275 }
276 // Save current serial number so that next copies of the same moment can start from here
277 CricketMomentsPack.nextSerial[CricketMomentsPack.totalMomentIds] = (serialNumber as UInt64)
278 // Initialize locked as false for a new Moment.
279 CricketMomentsPack.locked[CricketMomentsPack.totalMomentIds] = false
280 // Increment totalMomentIds
281 CricketMomentsPack.totalMomentIds = CricketMomentsPack.totalMomentIds + (1 as UInt64)
282 }
283
284 // Mint more NFTs for a particular momentId that already exists
285 access(all) fun mintOldNFTs(recipient: &{NonFungibleToken.CollectionPublic}, momentId:UInt64, serialQuantity: UInt64, metadata: {String : String}) {
286
287 var ipfs: String = metadata["ipfs"] ?? panic("IPFS url not available")
288 var serialNumber = CricketMomentsPack.nextSerial[momentId] ?? panic("momentId not present")
289 var isLocked = CricketMomentsPack.locked[momentId] ?? panic("momentId not present")
290 if (isLocked==true){
291 panic("Moment already locked. Can't mint any more NFTs for this momentId")
292 }
293
294 var i = 1 as UInt64
295 while i <= serialQuantity {
296 emit Minted(
297 id: CricketMomentsPack.totalSupply,
298 momentId:momentId,
299 serial:serialNumber,
300 ipfs:ipfs
301 )
302
303 // deposit it in the recipient's account using their reference
304 recipient.deposit(token: <-create CricketMomentsPack.NFT(
305 id: CricketMomentsPack.totalSupply,
306 momentId: momentId,
307 serial: serialNumber,
308 metadata: metadata
309 ))
310
311 serialNumber = serialNumber + (1 as UInt64)
312 i = i + 1 as UInt64
313 CricketMomentsPack.totalSupply = CricketMomentsPack.totalSupply + (1 as UInt64)
314 }
315 // Save current serial number so that next copies of the same moment can start from here
316 // No need to increment totalMomentIds
317 CricketMomentsPack.nextSerial[momentId] = serialNumber
318
319 }
320
321 // Lock a particular momentId, so that no more moments with this momentId can be minted.
322 access(all) fun lockMoment(momentId:UInt64) {
323
324 if (CricketMomentsPack.locked[momentId]==nil) {
325 panic("Moment not minted yet")
326 }
327 CricketMomentsPack.locked[momentId]=true
328 }
329 }
330
331 // fetch
332 // Get a reference to a CricketMomentsPack from an account's Collection, if available.
333 // If an account does not have a CricketMomentsPack.Collection, panic.
334 // If it has a collection but does not contain the itemID, return nil.
335 // If it has a collection and that collection contains the itemID, return a reference to that.
336 //
337 access(all) fun fetch(_ from: Address, id: UInt64): &CricketMomentsPack.NFT? {
338 let collection = getAccount(from).capabilities
339 .borrow<&CricketMomentsPack.Collection>(CricketMomentsPack.CollectionPublicPath)
340 ?? panic("Couldn't get collection")
341 // We trust CricketMomentsPack.Collection.borrowMoment to get the correct itemID
342 // (it checks it before returning it).
343 return collection.borrowCricketMoment(id: id)
344 }
345
346 // get next serial for a momentId (nextSerial -1 indicates number of copies minted for this momentId)
347 access(all) fun getNextSerial(momentId:UInt64): UInt64? {
348
349 if let nextSerial = self.nextSerial[momentId] {
350 return nextSerial
351 }
352 return nil
353 }
354
355 // check if a moment is locked. No more copies of a locked moment can be minted
356 access(all) fun isMomentLocked(momentId:UInt64): Bool? {
357
358 if let isLocked = self.locked[momentId] {
359 return isLocked
360 }
361 return nil
362 }
363
364 // initializer
365 //
366 init() {
367 // Set our named paths
368 self.CollectionStoragePath = /storage/CricketMomentsPackCollection
369 self.CollectionPublicPath = /public/CricketMomentsPackCollection
370 self.MinterStoragePath = /storage/CricketMomentsPackMinter
371
372 // Initialize the total supply
373 self.totalSupply = 0
374
375 // Initialize the total Moment IDs
376 self.totalMomentIds = 0
377
378 // Initialize locked and nextSerial as empty dictionaries
379 self.locked = {}
380 self.nextSerial = {}
381
382 // Create a Minter resource and save it to storage
383 let minter <- create NFTMinter()
384 self.account.storage.save(<-minter, to: self.MinterStoragePath)
385
386 emit ContractInitialized()
387 }
388}
389