Smart Contract
AthleteStudio
A.27ece19eff91bab0.AthleteStudio
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