Smart Contract

NBATopShotArena

A.27ece19eff91bab0.NBATopShotArena

Deployed

1d ago
Feb 27, 2026, 08:40:18 PM UTC

Dependents

0 imports
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