Smart Contract
NBATopShotArena
A.27ece19eff91bab0.NBATopShotArena
1import NonFungibleToken from 0x1d7e57aa55817448
2import MetadataViews from 0x1d7e57aa55817448
3import FungibleToken from 0xf233dcee88fe0abe
4import FreshmintMetadataViews from 0x0c82d33d4666f1f7
5
6pub contract NBATopShotArena: NonFungibleToken {
7
8 pub let version: String
9
10 pub event ContractInitialized()
11 pub event Withdraw(id: UInt64, from: Address?)
12 pub event Deposit(id: UInt64, to: Address?)
13 pub event Minted(id: UInt64, editionID: UInt64, serialNumber: UInt64)
14 pub event Burned(id: UInt64)
15 pub event EditionCreated(edition: Edition)
16 pub event EditionClosed(id: UInt64, size: UInt64)
17
18 pub let CollectionStoragePath: StoragePath
19 pub let CollectionPublicPath: PublicPath
20 pub let CollectionPrivatePath: PrivatePath
21 pub let AdminStoragePath: StoragePath
22
23 /// The total number of NBATopShotArena NFTs that have been minted.
24 ///
25 pub var totalSupply: UInt64
26
27 /// The total number of NBATopShotArena editions that have been created.
28 ///
29 pub var totalEditions: UInt64
30
31 /// A list of royalty recipients that is attached to all NFTs
32 /// minted by this contract.
33 ///
34 access(contract) let royalties: [MetadataViews.Royalty]
35
36 /// Return the royalty recipients for this contract.
37 ///
38 pub fun getRoyalties(): [MetadataViews.Royalty] {
39 return NBATopShotArena.royalties
40 }
41
42 /// The collection-level metadata for all NFTs minted by this contract.
43 ///
44 pub let collectionMetadata: MetadataViews.NFTCollectionDisplay
45
46 pub struct Metadata {
47
48 /// The core metadata fields for a NBATopShotArena NFT edition.
49 ///
50 pub let name: String
51 pub let description: String
52 pub let thumbnail: String
53 pub let asset: String
54 pub let assetType: String
55 pub let eventName: String
56 pub let eventDate: String
57 pub let externalURL: String
58
59 /// Optional attributes for a NBATopShotArena NFT edition.
60 ///
61 pub let attributes: {String: String}
62
63 init(
64 name: String,
65 description: String,
66 thumbnail: String,
67 asset: String,
68 assetType: String,
69 eventName: String,
70 eventDate: String,
71 externalURL: String,
72 attributes: {String: String}
73 ) {
74 self.name = name
75 self.description = description
76 self.thumbnail = thumbnail
77 self.asset = asset
78 self.assetType = assetType
79 self.eventName = eventName
80 self.eventDate = eventDate
81 self.externalURL = externalURL
82
83 self.attributes = attributes
84 }
85 }
86
87 pub struct Edition {
88
89 pub let id: UInt64
90
91 /// The maximum number of NFTs that can be minted in this edition.
92 ///
93 /// If nil, the edition has no size limit.
94 ///
95 pub let limit: UInt64?
96
97 /// The number of NFTs minted in this edition.
98 ///
99 /// This field is incremented each time a new NFT is minted.
100 /// It cannot exceed the limit defined above.
101 ///
102 pub var size: UInt64
103
104 /// The number of NFTs in this edition that have been burned.
105 ///
106 /// This field is incremented each time an NFT is burned.
107 ///
108 pub var burned: UInt64
109
110 /// Return the total supply of NFTs in this edition.
111 ///
112 /// The supply is the number of NFTs minted minus the number burned.
113 ///
114 pub fun supply(): UInt64 {
115 return self.size - self.burned
116 }
117
118 /// A flag indicating whether this edition is closed for minting.
119 ///
120 pub var isClosed: Bool
121
122 /// The metadata for this edition.
123 ///
124 pub let metadata: Metadata
125
126 init(
127 id: UInt64,
128 limit: UInt64?,
129 metadata: Metadata
130 ) {
131 self.id = id
132 self.limit = limit
133 self.metadata = metadata
134
135 self.size = 0
136 self.burned = 0
137
138 self.isClosed = false
139 }
140
141 /// Increment the size of this edition.
142 ///
143 access(contract) fun incrementSize() {
144 self.size = self.size + (1 as UInt64)
145 }
146
147 /// Increment the burn count for this edition.
148 ///
149 access(contract) fun incrementBurned() {
150 self.burned = self.burned + (1 as UInt64)
151 }
152
153 /// Close this edition and prevent further minting.
154 ///
155 /// Note: an edition is automatically closed when
156 /// it reaches its size limit, if defined.
157 ///
158 access(contract) fun close() {
159 self.isClosed = true
160 }
161 }
162
163 access(self) let editions: {UInt64: Edition}
164
165 pub fun getEdition(id: UInt64): Edition? {
166 return NBATopShotArena.editions[id]
167 }
168
169 /// This dictionary indexes editions by their mint ID.
170 ///
171 /// It is populated at mint time and used to prevent duplicate mints.
172 /// The mint ID can be any unique string value,
173 /// for example the hash of the edition metadata.
174 ///
175 access(self) let editionsByMintID: {String: UInt64}
176
177 pub fun getEditionByMintID(mintID: String): UInt64? {
178 return NBATopShotArena.editionsByMintID[mintID]
179 }
180
181 pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
182
183 pub let id: UInt64
184
185 pub let editionID: UInt64
186 pub let serialNumber: UInt64
187
188 init(
189 editionID: UInt64,
190 serialNumber: UInt64
191 ) {
192 self.id = self.uuid
193 self.editionID = editionID
194 self.serialNumber = serialNumber
195 }
196
197 /// Return the edition that this NFT belongs to.
198 ///
199 pub fun getEdition(): Edition {
200 return NBATopShotArena.getEdition(id: self.editionID)!
201 }
202
203 pub fun getViews(): [Type] {
204 return [
205 Type<MetadataViews.Display>(),
206 Type<MetadataViews.ExternalURL>(),
207 Type<MetadataViews.NFTView>(),
208 Type<MetadataViews.NFTCollectionDisplay>(),
209 Type<MetadataViews.NFTCollectionData>(),
210 Type<MetadataViews.Royalties>(),
211 Type<MetadataViews.Edition>()
212 ]
213 }
214
215 pub fun resolveView(_ view: Type): AnyStruct? {
216 let edition = self.getEdition()
217
218 switch view {
219 case Type<MetadataViews.Display>():
220 return self.resolveDisplay(edition.metadata)
221 case Type<MetadataViews.ExternalURL>():
222 return self.resolveExternalURL()
223 case Type<MetadataViews.NFTView>():
224 return self.resolveNFTView(edition.metadata)
225 case Type<MetadataViews.NFTCollectionDisplay>():
226 return self.resolveNFTCollectionDisplay()
227 case Type<MetadataViews.NFTCollectionData>():
228 return self.resolveNFTCollectionData()
229 case Type<MetadataViews.Royalties>():
230 return self.resolveRoyalties()
231 case Type<MetadataViews.Edition>():
232 return self.resolveEditionView(edition)
233 case Type<MetadataViews.Serial>():
234 return self.resolveSerialView(self.serialNumber)
235 }
236
237 return nil
238 }
239
240 pub fun resolveDisplay(_ metadata: Metadata): MetadataViews.Display {
241 return MetadataViews.Display(
242 name: metadata.name,
243 description: metadata.description,
244 thumbnail: FreshmintMetadataViews.ipfsFile(file: metadata.thumbnail)
245 )
246 }
247
248 pub fun resolveExternalURL(): MetadataViews.ExternalURL {
249 return MetadataViews.ExternalURL("https://nbatopshot.com")
250 }
251
252 pub fun resolveNFTView(_ metadata: Metadata): MetadataViews.NFTView {
253 return MetadataViews.NFTView(
254 id: self.id,
255 uuid: self.uuid,
256 display: self.resolveDisplay(metadata),
257 externalURL: self.resolveExternalURL(),
258 collectionData: self.resolveNFTCollectionData(),
259 collectionDisplay: self.resolveNFTCollectionDisplay(),
260 royalties : self.resolveRoyalties(),
261 traits: nil
262 )
263 }
264
265 pub fun resolveNFTCollectionDisplay(): MetadataViews.NFTCollectionDisplay {
266 return NBATopShotArena.collectionMetadata
267 }
268
269 pub fun resolveNFTCollectionData(): MetadataViews.NFTCollectionData {
270 return MetadataViews.NFTCollectionData(
271 storagePath: NBATopShotArena.CollectionStoragePath,
272 publicPath: NBATopShotArena.CollectionPublicPath,
273 providerPath: NBATopShotArena.CollectionPrivatePath,
274 publicCollection: Type<&NBATopShotArena.Collection{NBATopShotArena.NBATopShotArenaCollectionPublic}>(),
275 publicLinkedType: Type<&NBATopShotArena.Collection{NBATopShotArena.NBATopShotArenaCollectionPublic, NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>(),
276 providerLinkedType: Type<&NBATopShotArena.Collection{NBATopShotArena.NBATopShotArenaCollectionPublic, NonFungibleToken.CollectionPublic, NonFungibleToken.Provider, MetadataViews.ResolverCollection}>(),
277 createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
278 return <-NBATopShotArena.createEmptyCollection()
279 })
280 )
281 }
282
283 pub fun resolveRoyalties(): MetadataViews.Royalties {
284 return MetadataViews.Royalties(NBATopShotArena.getRoyalties())
285 }
286
287 pub fun resolveEditionView(_ edition: Edition): MetadataViews.Edition {
288 return MetadataViews.Edition(
289 name: "Edition",
290 number: self.serialNumber,
291 max: edition.size
292 )
293 }
294
295 pub fun resolveSerialView(_ serialNumber: UInt64): MetadataViews.Serial {
296 return MetadataViews.Serial(
297 number: serialNumber
298 )
299 }
300
301 destroy() {
302 NBATopShotArena.totalSupply = NBATopShotArena.totalSupply - (1 as UInt64)
303
304 // Update the burn count for the NFT's edition
305 let edition = self.getEdition()
306
307 edition.incrementBurned()
308
309 NBATopShotArena.editions[edition.id] = edition
310
311 emit Burned(id: self.id)
312 }
313 }
314
315 pub resource interface NBATopShotArenaCollectionPublic {
316 pub fun deposit(token: @NonFungibleToken.NFT)
317 pub fun getIDs(): [UInt64]
318 pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
319 pub fun borrowNBATopShotArena(id: UInt64): &NBATopShotArena.NFT? {
320 post {
321 (result == nil) || (result?.id == id):
322 "Cannot borrow NBATopShotArena reference: The ID of the returned reference is incorrect"
323 }
324 }
325 }
326
327 pub resource Collection: NBATopShotArenaCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
328
329 /// A dictionary of all NFTs in this collection indexed by ID.
330 ///
331 pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
332
333 init () {
334 self.ownedNFTs <- {}
335 }
336
337 /// Remove an NFT from the collection and move it to the caller.
338 ///
339 pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
340 let token <- self.ownedNFTs.remove(key: withdrawID)
341 ?? panic("Requested NFT to withdraw does not exist in this collection")
342
343 emit Withdraw(id: token.id, from: self.owner?.address)
344
345 return <- token
346 }
347
348 /// Deposit an NFT into this collection.
349 ///
350 pub fun deposit(token: @NonFungibleToken.NFT) {
351 let token <- token as! @NBATopShotArena.NFT
352
353 let id: UInt64 = token.id
354
355 // add the new token to the dictionary which removes the old one
356 let oldToken <- self.ownedNFTs[id] <- token
357
358 emit Deposit(id: id, to: self.owner?.address)
359
360 destroy oldToken
361 }
362
363 /// Return an array of the NFT IDs in this collection.
364 ///
365 pub fun getIDs(): [UInt64] {
366 return self.ownedNFTs.keys
367 }
368
369 /// Return a reference to an NFT in this collection.
370 ///
371 /// This function panics if the NFT does not exist in this collection.
372 ///
373 pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
374 return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
375 }
376
377 /// Return a reference to an NFT in this collection
378 /// typed as NBATopShotArena.NFT.
379 ///
380 /// This function returns nil if the NFT does not exist in this collection.
381 ///
382 pub fun borrowNBATopShotArena(id: UInt64): &NBATopShotArena.NFT? {
383 if self.ownedNFTs[id] != nil {
384 let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
385 return ref as! &NBATopShotArena.NFT
386 }
387
388 return nil
389 }
390
391 /// Return a reference to an NFT in this collection
392 /// typed as MetadataViews.Resolver.
393 ///
394 /// This function panics if the NFT does not exist in this collection.
395 ///
396 pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
397 let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
398 let nftRef = nft as! &NBATopShotArena.NFT
399 return nftRef as &AnyResource{MetadataViews.Resolver}
400 }
401
402 destroy() {
403 destroy self.ownedNFTs
404 }
405 }
406
407 /// Return a new empty collection.
408 ///
409 pub fun createEmptyCollection(): @NonFungibleToken.Collection {
410 return <- create Collection()
411 }
412
413 /// The administrator resource used to mint and reveal NFTs.
414 ///
415 pub resource Admin {
416
417 /// Create a new NFT edition.
418 ///
419 /// This function does not mint any NFTs. It only creates the
420 /// edition data that will later be associated with minted NFTs.
421 ///
422 pub fun createEdition(
423 mintID: String,
424 limit: UInt64?,
425 name: String,
426 description: String,
427 thumbnail: String,
428 asset: String,
429 assetType: String,
430 eventName: String,
431 eventDate: String,
432 externalURL: String,
433 attributes: {String: String}
434 ): UInt64 {
435 let metadata = Metadata(
436 name: name,
437 description: description,
438 thumbnail: thumbnail,
439 asset: asset,
440 assetType: assetType,
441 eventName: eventName,
442 eventDate: eventDate,
443 externalURL: externalURL,
444 attributes: attributes
445 )
446
447 // Prevent multiple editions from being minted with the same mint ID
448 assert(
449 NBATopShotArena.editionsByMintID[mintID] == nil,
450 message: "an edition has already been created with mintID=".concat(mintID)
451 )
452
453 let edition = Edition(
454 id: NBATopShotArena.totalEditions,
455 limit: limit,
456 metadata: metadata
457 )
458
459 // Save the edition
460 NBATopShotArena.editions[edition.id] = edition
461
462 // Update the mint ID index
463 NBATopShotArena.editionsByMintID[mintID] = edition.id
464
465 emit EditionCreated(edition: edition)
466
467 NBATopShotArena.totalEditions = NBATopShotArena.totalEditions + (1 as UInt64)
468
469 return edition.id
470 }
471
472 /// Close an existing edition.
473 ///
474 /// This prevents new NFTs from being minted into the edition.
475 /// An edition cannot be reopened after it is closed.
476 ///
477 pub fun closeEdition(editionID: UInt64) {
478 let edition = NBATopShotArena.editions[editionID]
479 ?? panic("edition does not exist")
480
481 // Prevent the edition from being closed more than once
482 assert(edition.isClosed == false, message: "edition is already closed")
483
484 edition.close()
485
486 // Save the updated edition
487 NBATopShotArena.editions[editionID] = edition
488
489 emit EditionClosed(id: edition.id, size: edition.size)
490 }
491
492 /// Mint a new NFT.
493 ///
494 /// This function will mint the next NFT in this edition
495 /// and automatically assign the serial number.
496 ///
497 /// This function will panic if the edition has already
498 /// reached its maximum size.
499 ///
500 pub fun mintNFT(editionID: UInt64): @NBATopShotArena.NFT {
501 let edition = NBATopShotArena.editions[editionID]
502 ?? panic("edition does not exist")
503
504 // Do not mint into a closed edition
505 assert(edition.isClosed == false, message: "edition is closed for minting")
506
507 // Increase the edition size by one
508 edition.incrementSize()
509
510 // The NFT serial number is the new edition size
511 let serialNumber = edition.size
512
513 let nft <- create NBATopShotArena.NFT(
514 editionID: editionID,
515 serialNumber: serialNumber
516 )
517
518 emit Minted(id: nft.id, editionID: editionID, serialNumber: serialNumber)
519
520 // Close the edition if it reaches its size limit
521 if let limit = edition.limit {
522 if edition.size == limit {
523 edition.close()
524
525 emit EditionClosed(id: edition.id, size: edition.size)
526 }
527 }
528
529 // Save the updated edition
530 NBATopShotArena.editions[editionID] = edition
531
532 NBATopShotArena.totalSupply = NBATopShotArena.totalSupply + (1 as UInt64)
533
534 return <- nft
535 }
536 }
537
538 /// Return a public path that is scoped to this contract.
539 ///
540 pub fun getPublicPath(suffix: String): PublicPath {
541 return PublicPath(identifier: "NBATopShotArena_".concat(suffix))!
542 }
543
544 /// Return a private path that is scoped to this contract.
545 ///
546 pub fun getPrivatePath(suffix: String): PrivatePath {
547 return PrivatePath(identifier: "NBATopShotArena_".concat(suffix))!
548 }
549
550 /// Return a storage path that is scoped to this contract.
551 ///
552 pub fun getStoragePath(suffix: String): StoragePath {
553 return StoragePath(identifier: "NBATopShotArena_".concat(suffix))!
554 }
555
556 /// Return a collection name with an optional bucket suffix.
557 ///
558 pub fun makeCollectionName(bucketName maybeBucketName: String?): String {
559 if let bucketName = maybeBucketName {
560 return "Collection_".concat(bucketName)
561 }
562
563 return "Collection"
564 }
565
566 /// Return a queue name with an optional bucket suffix.
567 ///
568 pub fun makeQueueName(bucketName maybeBucketName: String?): String {
569 if let bucketName = maybeBucketName {
570 return "Queue_".concat(bucketName)
571 }
572
573 return "Queue"
574 }
575
576 priv fun initAdmin(admin: AuthAccount) {
577 // Create an empty collection and save it to storage
578 let collection <- NBATopShotArena.createEmptyCollection()
579
580 admin.save(<- collection, to: NBATopShotArena.CollectionStoragePath)
581
582 admin.link<&NBATopShotArena.Collection>(NBATopShotArena.CollectionPrivatePath, target: NBATopShotArena.CollectionStoragePath)
583
584 admin.link<&NBATopShotArena.Collection{NonFungibleToken.CollectionPublic, NBATopShotArena.NBATopShotArenaCollectionPublic, MetadataViews.ResolverCollection}>(NBATopShotArena.CollectionPublicPath, target: NBATopShotArena.CollectionStoragePath)
585
586 // Create an admin resource and save it to storage
587 let adminResource <- create Admin()
588
589 admin.save(<- adminResource, to: self.AdminStoragePath)
590 }
591
592 init(collectionMetadata: MetadataViews.NFTCollectionDisplay, royalties: [MetadataViews.Royalty]) {
593
594 self.version = "0.7.0"
595
596 self.CollectionPublicPath = NBATopShotArena.getPublicPath(suffix: "Collection")
597 self.CollectionStoragePath = NBATopShotArena.getStoragePath(suffix: "Collection")
598 self.CollectionPrivatePath = NBATopShotArena.getPrivatePath(suffix: "Collection")
599
600 self.AdminStoragePath = NBATopShotArena.getStoragePath(suffix: "Admin")
601
602 self.royalties = royalties
603 self.collectionMetadata = collectionMetadata
604
605 self.totalSupply = 0
606 self.totalEditions = 0
607
608 self.editions = {}
609 self.editionsByMintID = {}
610
611 self.initAdmin(admin: self.account)
612
613 emit ContractInitialized()
614 }
615}
616