Smart Contract

AthleteStudio

A.27ece19eff91bab0.AthleteStudio

Deployed

18h ago
Feb 28, 2026, 12:07:03 AM UTC

Dependents

0 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import MetadataViews from 0x1d7e57aa55817448
3import FungibleToken from 0xf233dcee88fe0abe
4import AthleteStudioMintCache from 0x27ece19eff91bab0
5
6pub contract AthleteStudio: 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
17    pub let CollectionStoragePath: StoragePath
18    pub let CollectionPublicPath: PublicPath
19    pub let CollectionPrivatePath: PrivatePath
20    pub let AdminStoragePath: StoragePath
21
22    /// The total number of Athlete Studio NFTs that have been minted.
23    ///
24    pub var totalSupply: UInt64
25
26    /// The total number of Athlete Studio NFT editions that have been created.
27    ///
28    pub var totalEditions: UInt64
29
30    /// The royalty information for all Athlete Studio NFTs.
31    ///
32    pub var royalty: MetadataViews.Royalty?
33
34    pub struct Metadata {
35
36        pub let name: String
37        pub let description: String
38        pub let thumbnail: String
39        pub let asset: String
40        pub let assetType: String
41        pub let athleteID: Int
42        pub let athleteName: String
43        pub let athleteURL: String
44        pub let itemType: String
45        pub let itemCategory: String
46        pub let series: String
47        pub let eventName: String
48        pub let eventDate: String
49        pub let eventType: String
50        pub let signed: Bool
51
52        init(
53            name: String,
54            description: String,
55            thumbnail: String,
56            asset: String,
57            assetType: String,
58            athleteID: Int,
59            athleteName: String,
60            athleteURL: String,
61            itemType: String,
62            itemCategory: String,
63            series: String,
64            eventName: String,
65            eventDate: String,
66            eventType: String,
67            signed: Bool,
68        ) {
69            self.name = name
70            self.description = description
71            self.thumbnail = thumbnail
72            self.asset = asset
73            self.assetType = assetType
74            self.athleteID = athleteID
75            self.athleteName = athleteName
76            self.athleteURL = athleteURL
77            self.itemType = itemType
78            self.itemCategory = itemCategory
79            self.series = series
80            self.eventName = eventName
81            self.eventDate = eventDate
82            self.eventType = eventType
83            self.signed = signed
84        }
85    }
86
87    pub struct Edition {
88
89        pub let id: UInt64
90
91        /// The maximum size of this edition.
92        ///
93        pub let size: UInt64
94
95        /// The number of NFTs minted in this edition.
96        ///
97        /// The count cannot exceed the edition size.
98        ///
99        pub var count: UInt64
100
101        /// The metadata for this edition.
102        ///
103        pub let metadata: Metadata
104
105        init(
106            id: UInt64,
107            size: UInt64,
108            metadata: Metadata
109        ) {
110            self.id = id
111            self.size = size
112            self.metadata = metadata
113
114            // An edition starts with a count of zero
115            self.count = 0
116        }
117
118        /// Increment the NFT count of this edition.
119        ///
120        /// The count cannot exceed the edition size.
121        ///
122        access(contract) fun incrementCount() {
123            post {
124                self.count <= self.size: "edition has already reached its maximum size"
125            }
126
127            self.count = self.count + (1 as UInt64)
128        }
129    }
130
131    access(self) let editions: {UInt64: Edition}
132
133    pub fun getEdition(id: UInt64): Edition? {
134        return AthleteStudio.editions[id]
135    }
136
137    pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
138
139        pub let id: UInt64
140
141        pub let editionID: UInt64
142        pub let serialNumber: UInt64
143
144        init(
145            editionID: UInt64,
146            serialNumber: UInt64
147        ) {
148            self.id = self.uuid
149            self.editionID = editionID
150            self.serialNumber = serialNumber
151        }
152
153        /// Return the edition that this NFT belongs to.
154        ///
155        pub fun getEdition(): Edition {
156            return AthleteStudio.getEdition(id: self.editionID)!
157        }
158
159        pub fun getViews(): [Type] {
160            return [
161                Type<MetadataViews.NFTView>(),
162                Type<MetadataViews.Display>(),
163                Type<MetadataViews.ExternalURL>(),
164                Type<MetadataViews.NFTCollectionDisplay>(),
165                Type<MetadataViews.NFTCollectionData>(),
166                Type<MetadataViews.Royalties>(),
167                Type<MetadataViews.Edition>(),
168                Type<MetadataViews.Serial>(),
169                Type<MetadataViews.Media>(),
170                Type<MetadataViews.Medias>()
171            ]
172        }
173
174        pub fun resolveView(_ view: Type): AnyStruct? {
175            let edition = self.getEdition()
176
177            switch view {
178                case Type<MetadataViews.NFTView>():
179                    return self.resolveNFTView(edition.metadata)
180                case Type<MetadataViews.Display>():
181                    return self.resolveDisplay(edition.metadata)
182                case Type<MetadataViews.ExternalURL>():
183                    return self.resolveExternalURL(edition.metadata)
184                case Type<MetadataViews.NFTCollectionDisplay>():
185                    return self.resolveNFTCollectionDisplay()
186                case Type<MetadataViews.NFTCollectionData>():
187                    return self.resolveNFTCollectionData()
188                case Type<MetadataViews.Royalties>():
189                    return self.resolveRoyalties()
190                case Type<MetadataViews.Edition>():
191                    return self.resolveEditionView(edition)
192                case Type<MetadataViews.Serial>():
193                    return self.resolveSerialView(self.serialNumber)
194                case Type<MetadataViews.Media>():
195                    return self.resolveMedia(edition.metadata)
196                case Type<MetadataViews.Medias>():
197                    return self.resolveMedias(edition.metadata)
198            }
199
200            return nil
201        }
202
203        pub fun resolveNFTView(_ metadata: Metadata): MetadataViews.NFTView {
204            return MetadataViews.NFTView(
205                id: self.id,
206                uuid: self.uuid,
207                display: self.resolveDisplay(metadata),
208                externalURL: self.resolveExternalURL(metadata),
209                collectionData: self.resolveNFTCollectionData(),
210                collectionDisplay: self.resolveNFTCollectionDisplay(),
211                royalties : self.resolveRoyalties(),
212                traits: nil
213            )
214        }
215
216        pub fun resolveDisplay(_ metadata: Metadata): MetadataViews.Display {
217            return MetadataViews.Display(
218                name: metadata.name,
219                description: metadata.description,
220                thumbnail: MetadataViews.IPFSFile(cid: metadata.thumbnail, path: nil)
221            )
222        }
223
224        pub fun resolveExternalURL(_ metadata: Metadata): MetadataViews.ExternalURL {
225            return MetadataViews.ExternalURL(metadata.athleteURL)
226        }
227
228        pub fun resolveNFTCollectionDisplay(): MetadataViews.NFTCollectionDisplay {
229            let media = MetadataViews.Media(
230                file: MetadataViews.HTTPFile(
231                    url: "https://d3w5a827wx4ops.cloudfront.net/athlete-studio-logo-light.png"
232                ),
233                mediaType: "image/png"
234            )
235
236            return MetadataViews.NFTCollectionDisplay(
237                name: "Athlete Studio NFTs",
238                description: "Officially licensed NFTs from Pro Athletes.",
239                externalURL: MetadataViews.ExternalURL("https://athlete.studio"),
240                squareImage: media,
241                bannerImage: media,
242                socials: {
243                    "twitter": MetadataViews.ExternalURL("https://athlete.studio/twitter"),
244                    "instagram": MetadataViews.ExternalURL("https://athlete.studio/instagram")
245                }
246            )
247        }
248
249        pub fun resolveNFTCollectionData(): MetadataViews.NFTCollectionData {
250            return MetadataViews.NFTCollectionData(
251                storagePath: AthleteStudio.CollectionStoragePath,
252                publicPath: AthleteStudio.CollectionPublicPath,
253                providerPath: AthleteStudio.CollectionPrivatePath,
254                publicCollection: Type<&AthleteStudio.Collection{AthleteStudio.AthleteStudioCollectionPublic}>(),
255                publicLinkedType: Type<&AthleteStudio.Collection{AthleteStudio.AthleteStudioCollectionPublic, NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>(),
256                providerLinkedType: Type<&AthleteStudio.Collection{AthleteStudio.AthleteStudioCollectionPublic, NonFungibleToken.CollectionPublic, NonFungibleToken.Provider, MetadataViews.ResolverCollection}>(),
257                createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
258                    return <-AthleteStudio.createEmptyCollection()
259                })
260            )
261        }
262
263        pub fun resolveRoyalties(): MetadataViews.Royalties {
264            // Return the Athlete Studio royalty if one is set
265            if let royalty = AthleteStudio.royalty {
266                return MetadataViews.Royalties([royalty])
267            }
268
269            return MetadataViews.Royalties([])
270        }
271
272        pub fun resolveEditionView(_ edition: Edition): MetadataViews.Edition {
273            return MetadataViews.Edition(
274                name: "Edition",
275                number: self.serialNumber,
276                max: edition.size
277            )
278        }
279
280        pub fun resolveSerialView(_ serialNumber: UInt64): MetadataViews.Serial {
281            return MetadataViews.Serial(
282                number: serialNumber
283            )
284        }
285
286        pub fun resolveMedia(_ metadata: Metadata): MetadataViews.Media {
287            return MetadataViews.Media(
288                file: MetadataViews.IPFSFile(cid: metadata.asset, path: nil),
289                mediaType: metadata.assetType
290            )
291        }
292
293        pub fun resolveMedias(_ metadata: Metadata): MetadataViews.Medias {
294            return MetadataViews.Medias(
295                items: [
296                    MetadataViews.Media(
297                        file: MetadataViews.IPFSFile(cid: metadata.asset, path: nil),
298                        mediaType: metadata.assetType
299                    ),
300                    MetadataViews.Media(
301                        file: MetadataViews.IPFSFile(cid: metadata.thumbnail, path: nil),
302                        mediaType: "image/png"
303                    )
304                ]
305            )
306        }
307
308        destroy() {
309            AthleteStudio.totalSupply = AthleteStudio.totalSupply - (1 as UInt64)
310
311            emit Burned(id: self.id)
312        }
313    }
314
315    pub resource interface AthleteStudioCollectionPublic {
316        pub fun deposit(token: @NonFungibleToken.NFT)
317        pub fun getIDs(): [UInt64]
318        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
319        pub fun borrowAthleteStudio(id: UInt64): &AthleteStudio.NFT? {
320            post {
321                (result == nil) || (result?.id == id):
322                    "Cannot borrow AthleteStudio reference: The ID of the returned reference is incorrect"
323            }
324        }
325    }
326
327    pub resource Collection: AthleteStudioCollectionPublic, 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! @AthleteStudio.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 AthleteStudio.NFT.
379        ///
380        /// This function returns nil if the NFT does not exist in this collection.
381        ///
382        pub fun borrowAthleteStudio(id: UInt64): &AthleteStudio.NFT? {
383            if self.ownedNFTs[id] != nil {
384                let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
385                return ref as! &AthleteStudio.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! &AthleteStudio.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            size: UInt64,
425            name: String,
426            description: String,
427            thumbnail: String,
428            asset: String,
429            assetType: String,
430            athleteID: Int,
431            athleteName: String,
432            athleteURL: String,
433            itemType: String,
434            itemCategory: String,
435            series: String,
436            eventName: String,
437            eventDate: String,
438            eventType: String,
439            signed: Bool,
440        ): UInt64 {
441            // Prevent multiple editions from being minted with the same mint ID
442            assert(
443                AthleteStudioMintCache.getEditionByMintID(mintID: mintID) == nil,
444                message: "an edition has already been created with mintID=".concat(mintID)
445            )
446
447            let metadata = Metadata(
448                name: name,
449                description: description,
450                thumbnail: thumbnail,
451                asset: asset,
452                assetType: assetType,
453                athleteID: athleteID,
454                athleteName: athleteName,
455                athleteURL: athleteURL,
456                itemType: itemType,
457                itemCategory: itemCategory,
458                series: series,
459                eventName: eventName,
460                eventDate: eventDate,
461                eventType: eventType,
462                signed: signed,
463            )
464
465            let edition = Edition(
466                id: AthleteStudio.totalEditions,
467                size: size,
468                metadata: metadata
469            )
470
471            AthleteStudio.editions[edition.id] = edition
472
473            emit EditionCreated(edition: edition)
474
475            // Update the mint ID index
476            AthleteStudioMintCache.insertEditionMintID(mintID: mintID, editionID: edition.id)
477
478            AthleteStudio.totalEditions = AthleteStudio.totalEditions + (1 as UInt64)
479
480            return edition.id
481        }
482
483        /// Mint a new NFT.
484        ///
485        /// This function will mint the next NFT in this edition
486        /// and automatically assign the serial number.
487        ///
488        /// This function will panic if the edition has already
489        /// reached its maximum size.
490        ///
491        pub fun mintNFT(editionID: UInt64): @AthleteStudio.NFT {
492            let edition = AthleteStudio.editions[editionID]
493                ?? panic("edition does not exist")
494
495            // Increase the edition count by one
496            edition.incrementCount()
497
498            // The NFT serial number is the new edition count
499            let serialNumber = edition.count
500
501            let nft <- create AthleteStudio.NFT(
502                editionID: editionID,
503                serialNumber: serialNumber
504            )
505
506            // Save the updated edition
507            AthleteStudio.editions[editionID] = edition
508
509            emit Minted(id: nft.id, editionID: editionID, serialNumber: serialNumber)
510
511            AthleteStudio.totalSupply = AthleteStudio.totalSupply + (1 as UInt64)
512
513            return <- nft
514        }
515
516        /// Set the royalty percentage and receiver for all Athlete Studio NFTs.
517        ///
518        pub fun setRoyalty(royalty: MetadataViews.Royalty) {
519            AthleteStudio.royalty = royalty
520        }
521    }
522
523    /// Return a public path that is scoped to this contract.
524    ///
525    pub fun getPublicPath(suffix: String): PublicPath {
526        return PublicPath(identifier: "AthleteStudio_".concat(suffix))!
527    }
528
529    /// Return a private path that is scoped to this contract.
530    ///
531    pub fun getPrivatePath(suffix: String): PrivatePath {
532        return PrivatePath(identifier: "AthleteStudio_".concat(suffix))!
533    }
534
535    /// Return a storage path that is scoped to this contract.
536    ///
537    pub fun getStoragePath(suffix: String): StoragePath {
538        return StoragePath(identifier: "AthleteStudio_".concat(suffix))!
539    }
540
541    priv fun initAdmin(admin: AuthAccount) {
542        // Create an empty collection and save it to storage
543        let collection <- AthleteStudio.createEmptyCollection()
544
545        admin.save(<- collection, to: AthleteStudio.CollectionStoragePath)
546
547        admin.link<&AthleteStudio.Collection>(AthleteStudio.CollectionPrivatePath, target: AthleteStudio.CollectionStoragePath)
548
549        admin.link<&AthleteStudio.Collection{NonFungibleToken.CollectionPublic, AthleteStudio.AthleteStudioCollectionPublic, MetadataViews.ResolverCollection}>(AthleteStudio.CollectionPublicPath, target: AthleteStudio.CollectionStoragePath)
550
551        // Create an admin resource and save it to storage
552        let adminResource <- create Admin()
553
554        admin.save(<- adminResource, to: self.AdminStoragePath)
555    }
556
557    init() {
558
559        self.version = "0.0.24"
560
561        self.CollectionPublicPath = AthleteStudio.getPublicPath(suffix: "Collection")
562        self.CollectionStoragePath = AthleteStudio.getStoragePath(suffix: "Collection")
563        self.CollectionPrivatePath = AthleteStudio.getPrivatePath(suffix: "Collection")
564
565        self.AdminStoragePath = AthleteStudio.getStoragePath(suffix: "Admin")
566
567        self.totalSupply = 0
568        self.totalEditions = 0
569
570        self.editions = {}
571
572        self.royalty = nil
573
574        self.initAdmin(admin: self.account)
575
576        emit ContractInitialized()
577    }
578}
579