Smart Contract
Sportvatar
A.ca5c31c0c03e11be.Sportvatar
1import FungibleToken from 0xf233dcee88fe0abe
2import ViewResolver from 0x1d7e57aa55817448
3import NonFungibleToken from 0x1d7e57aa55817448
4import FlowToken from 0x1654653399040a61
5import SportvatarTemplate from 0xca5c31c0c03e11be
6import MetadataViews from 0x1d7e57aa55817448
7import SportvatarPack from 0xca5c31c0c03e11be
8import Sportbit from 0xca5c31c0c03e11be
9import FlovatarComponentTemplate from 0x921ea449dffec68a
10import FlovatarComponent from 0x921ea449dffec68a
11
12/*
13
14 The contract that defines the Sportvatar NFT and a Collection to manage them
15
16
17This contract contains also the Admin resource that can be used to manage and generate the Sportvatar Templates.
18
19 */
20
21access(all)
22contract Sportvatar: NonFungibleToken{
23 access(all)
24 let CollectionStoragePath: StoragePath
25
26 access(all)
27 let CollectionPublicPath: PublicPath
28
29 access(all)
30 let AdminStoragePath: StoragePath
31
32 // These will be used in the Marketplace to pay out
33 // royalties to the creator and to the marketplace
34 access(account)
35 var royaltyCut: UFix64
36
37 access(account)
38 var marketplaceCut: UFix64
39
40 // Here we keep track of all the Sportvatar unique combinations and names
41 // that people will generate to make sure that there are no duplicates
42 access(all)
43 var totalSupply: UInt64
44
45 access(contract)
46 let mintedCombinations:{ String: Bool}
47
48 access(contract)
49 let mintedNames:{ String: Bool}
50
51 access(all)
52 event ContractInitialized()
53
54 access(all)
55 event Withdraw(id: UInt64, from: Address?)
56
57 access(all)
58 event Deposit(id: UInt64, to: Address?)
59
60 access(all)
61 event Created(id: UInt64, mint: UInt64, series: UInt64, address: Address)
62
63 access(all)
64 event Updated(id: UInt64)
65
66 access(all)
67 event Destroyed(id: UInt64)
68
69 access(all)
70 event NameSet(id: UInt64, name: String)
71
72 access(all)
73 event PositionChanged(id: UInt64, position: String)
74
75 access(all)
76 event StoryAdded(id: UInt64, story: String)
77
78 access(all)
79 struct Royalties{
80 access(all)
81 let royalty: [Royalty]
82
83 init(royalty: [Royalty]){
84 self.royalty = royalty
85 }
86 }
87
88 access(all)
89 enum RoyaltyType: UInt8{
90 access(all)
91 case fixed
92
93 access(all)
94 case percentage
95 }
96
97 access(all)
98 struct Royalty{
99 access(all)
100 let wallet: Capability<&{FungibleToken.Receiver}>
101
102 access(all)
103 let cut: UFix64
104
105 //can be percentage
106 access(all)
107 let type: RoyaltyType
108
109 init(wallet: Capability<&{FungibleToken.Receiver}>, cut: UFix64, type: RoyaltyType){
110 if !wallet.check(){
111 panic("Capability not valid!")
112 }
113 self.wallet = wallet
114 self.cut = cut
115 self.type = type
116 }
117 }
118
119 //Randomize code gently provided by @bluesign
120 access(all)
121 struct RandomInt{
122 access(self)
123 var value: UInt64?
124
125 access(self)
126 let maxValue: UInt64
127
128 access(self)
129 let minValue: UInt64
130
131 access(self)
132 let field: String
133
134 access(self)
135 let uuid: UInt64
136
137 access(all)
138 init(uuid: UInt64, field: String, minValue: UInt64, maxValue: UInt64){
139 self.uuid = uuid
140 self.field = field
141 self.minValue = minValue
142 self.maxValue = maxValue
143 self.value = nil
144 }
145
146 access(all)
147 fun getValue(): UInt64{
148 if let value = self.value{
149 return value
150 }
151 let h: [UInt8] = HashAlgorithm.SHA3_256.hash(self.uuid.toBigEndianBytes())
152 let f: [UInt8] = HashAlgorithm.SHA3_256.hash(self.field.utf8)
153 var id = (getBlock(at: getCurrentBlock().height)!).id
154 var random: UInt64 = 0
155 var i = 0
156 while i < 8{
157 random = random + (UInt64(id[i]) ^ UInt64(h[i]) ^ UInt64(f[i]))
158 random = random << 8
159 i = i + 1
160 }
161 self.value = self.minValue + random % (self.maxValue - self.minValue)
162 return self.minValue + random % (self.maxValue - self.minValue)
163 }
164 }
165
166 // The public interface can show metadata and the content for the Sportvatar.
167 // In addition to it, it provides methods to access the additional optional
168 // components (accessory, hat, eyeglasses, background) for everyone.
169 access(all)
170 resource interface Public{
171 access(all)
172 let id: UInt64
173
174 access(all)
175 let mint: UInt64
176
177 access(all)
178 let series: UInt64
179
180 access(all)
181 let combination: String
182
183 access(all)
184 let rarity: String
185
186 access(all)
187 let creatorAddress: Address
188
189 access(all)
190 let createdAt: UFix64
191
192 access(all)
193 let createdAtBlock: UInt64
194
195 access(contract)
196 let royalties: Royalties
197
198 // these three are added because I think they will be in the standard. At least Dieter thinks it will be needed
199 access(contract)
200 var name: String
201
202 access(all)
203 let description: String
204
205 access(all)
206 let schema: String?
207
208 access(all)
209 fun getName(): String
210
211 access(all)
212 fun getSvg(): String
213
214 access(all)
215 fun getRoyalties(): Royalties
216
217 access(all)
218 fun getBio():{ String: String}
219
220 access(all)
221 fun getMetadata():{ String: String}
222
223 access(all)
224 fun getStats():{ String: UInt32}
225
226 access(all)
227 fun getLayers():{ UInt32: UInt64?}
228
229 access(all)
230 fun getAccessories(): [UInt64]
231
232 access(all)
233 fun getSeries(): SportvatarTemplate.SeriesData?
234
235 access(all)
236 fun getFlovatarBackground(): UInt64?
237 }
238
239 //The private interface can update the Accessory, Hat, Eyeglasses and Background
240 //for the Sportvatar and is accessible only to the owner of the NFT
241 access(all)
242 resource interface Private{
243 access(all)
244 fun setName(name: String): String
245
246 access(all)
247 fun addStory(text: String): String
248
249 access(all)
250 fun setPosition(latitude: Fix64, longitude: Fix64): String
251
252 access(all)
253 fun setSportbit(layer: UInt32, sportbit: @Sportbit.NFT): @Sportbit.NFT?
254
255 access(all)
256 fun removeAccessory(layer: UInt32): @Sportbit.NFT?
257
258 access(all)
259 fun setFlovatarBackground(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?
260
261 access(all)
262 fun removeFlovatarBackground(): @FlovatarComponent.NFT?
263 }
264
265 //The NFT resource that implements both Private and Public interfaces
266 access(all)
267 resource NFT: NonFungibleToken.NFT, Public, Private, ViewResolver.Resolver{
268 access(all)
269 let id: UInt64
270
271 access(all)
272 let mint: UInt64
273
274 access(all)
275 let series: UInt64
276
277 access(all)
278 let combination: String
279
280 access(all)
281 let rarity: String
282
283 access(all)
284 let creatorAddress: Address
285
286 access(all)
287 let createdAt: UFix64
288
289 access(all)
290 let createdAtBlock: UInt64
291
292 access(contract)
293 let royalties: Royalties
294
295 access(contract)
296 var name: String
297
298 access(all)
299 let description: String
300
301 access(all)
302 let schema: String?
303
304 access(self)
305 let bio:{ String: String}
306
307 access(self)
308 let metadata:{ String: String}
309
310 access(self)
311 let stats:{ String: UInt32}
312
313 access(self)
314 let layers:{ UInt32: UInt64?}
315
316 access(self)
317 let accessories: @{UInt32: Sportbit.NFT}
318
319 access(contract)
320 var background: @FlovatarComponent.NFT?
321
322 init(series: UInt64, layers:{ UInt32: UInt64?}, metadata:{ String: String}, stats:{ String: UInt32}, creatorAddress: Address, royalties: Royalties, rarity: String){
323 Sportvatar.totalSupply = Sportvatar.totalSupply + UInt64(1)
324 SportvatarTemplate.increaseTotalMintedCollectibles(series: series)
325 let coreLayers:{ UInt32: UInt64} = Sportvatar.getCoreLayers(series: series, layers: layers)
326 self.id = Sportvatar.totalSupply
327 self.mint = SportvatarTemplate.getTotalMintedCollectibles(series: series)!
328 self.series = series
329 self.combination = Sportvatar.getCombinationString(series: series, layers: coreLayers)
330 self.creatorAddress = creatorAddress
331 self.createdAt = getCurrentBlock().timestamp
332 self.createdAtBlock = getCurrentBlock().height
333 self.royalties = royalties
334 self.schema = nil
335 self.name = ""
336 self.description = ""
337 self.bio ={}
338 self.metadata = metadata
339 self.stats = stats
340 self.layers = layers
341 self.accessories <-{}
342 self.rarity = rarity
343 self.background <- nil
344 }
345
346 access(all)
347 fun getID(): UInt64{
348 return self.id
349 }
350
351 access(all)
352 fun getMetadata():{ String: String}{
353 return self.metadata
354 }
355
356 access(all)
357 fun getStats():{ String: UInt32}{
358 return self.createdAtBlock < getCurrentBlock().height ? self.stats :{}
359 }
360
361 access(all)
362 fun getRoyalties(): Royalties{
363 return self.royalties
364 }
365
366 access(all)
367 fun getBio():{ String: String}{
368 return self.bio
369 }
370
371 access(all)
372 fun getName(): String{
373 return self.name
374 }
375
376 access(all)
377 fun getSeries(): SportvatarTemplate.SeriesData?{
378 return SportvatarTemplate.getSeries(id: self.series)
379 }
380
381 // This will allow to change the Name of the Sportvatar only once.
382 // It checks for the current name is empty, otherwise it will throw an error.
383 access(all)
384 fun setName(name: String): String{
385 pre{
386 // TODO: Make sure that the text of the name is sanitized
387 //and that bad words are not accepted?
388 name.length > 2:
389 "The name is too short"
390 name.length < 32:
391 "The name is too long"
392 self.name == "":
393 "The name has already been set"
394 //vault.balance == 100.0 : "The amount of $DUST is not correct"
395 //vault.isInstance(Type<@SportvatarDustToken.Vault>()) : "Vault not of the right Token Type"
396 }
397
398 // Makes sure that the name is available and not taken already
399 if Sportvatar.checkNameAvailable(name: name) == false{
400 panic("This name has already been taken")
401 }
402
403 //destroy vault
404 //self.name = name
405
406 // Adds the name to the array to remember it
407 //Sportvatar.addMintedName(name: name)
408 //emit NameSet(id: self.id, name: name)
409 return self.name
410 }
411
412 // This will allow to add a text Story to the Sportvatar Bio.
413 // The String will be concatenated each time.
414 // There is a limit of 300 characters per story but there is no limit in the full concatenated story length
415 access(all)
416 fun addStory(text: String): String{
417 pre{
418 // TODO: Make sure that the text of the name is sanitized
419 //and that bad words are not accepted?
420 text.length > 0:
421 "The text is too short"
422 text.length <= 300:
423 "The text is too long"
424 //vault.balance == 50.0 : "The amount of $DUST is not correct"
425 //vault.isInstance(Type<@SportvatarDustToken.Vault>()) : "Vault not of the right Token Type"
426 }
427
428 //destroy vault
429 //let currentStory: String = self.bio["story"] ?? ""
430 //let story: String = currentStory.concat(" ").concat(text)
431 //self.bio.insert(key: "story", story)
432
433 //emit StoryAdded(id: self.id, story: story)
434
435 //return story
436 return ""
437 }
438
439 // This will allow to set the GPS location of a Sportvatar
440 // It can be run multiple times and each time it will override the previous state
441 access(all)
442 fun setPosition(latitude: Fix64, longitude: Fix64): String{
443 pre{
444 latitude >= -90.0:
445 "The latitude is out of range"
446 latitude <= 90.0:
447 "The latitude is out of range"
448 longitude >= -180.0:
449 "The longitude is out of range"
450 longitude <= 180.0:
451 "The longitude is out of range"
452 //vault.balance == 10.0 : "The amount of $DUST is not correct"
453 //vault.isInstance(Type<@SportvatarDustToken.Vault>()) : "Vault not of the right Token Type"
454 }
455
456 //destroy vault
457 //let position: String = latitude.toString().concat(",").concat(longitude.toString())
458 //self.bio.insert(key: "position", position)
459
460 //emit PositionChanged(id: self.id, position: position)
461
462 //return position
463 return ""
464 }
465
466 access(all)
467 fun getLayers():{ UInt32: UInt64?}{
468 return self.layers
469 }
470
471 access(all)
472 fun getAccessories(): [UInt64]{
473 let accessoriesIds: [UInt64] = []
474 for k in self.accessories.keys{
475 let accessoryId = self.accessories[k]?.id
476 if accessoryId != nil{
477 accessoriesIds.append(accessoryId!)
478 }
479 }
480 return accessoriesIds
481 }
482
483 // This will allow to change the Accessory of the Sportvatar any time.
484 // It checks for the right category and series before executing.
485 access(all)
486 fun setSportbit(layer: UInt32, sportbit: @Sportbit.NFT): @Sportbit.NFT?{
487
488 if(sportbit.getSeries() != self.series) {
489 panic("The accessory belongs to a different series")
490 }
491 if SportvatarTemplate.isCollectibleLayerAccessory(layer: layer, series: self.series){
492 emit Updated(id: self.id)
493 self.layers[layer] = sportbit.templateId
494 let oldAccessory <- self.accessories[layer] <- sportbit
495 return <-oldAccessory
496 }
497 panic("The Layer is out of range or it's not an accessory")
498 }
499
500 // This will allow to remove the Accessory of the Sportvatar any time.
501 access(all)
502 fun removeAccessory(layer: UInt32): @Sportbit.NFT?{
503 if SportvatarTemplate.isCollectibleLayerAccessory(layer: layer, series: self.series){
504 emit Updated(id: self.id)
505 self.layers[layer] = nil
506 let accessory <- self.accessories[layer] <- nil
507 return <-accessory
508 }
509 panic("The Layer is out of range or it's not an accessory")
510 }
511
512 access(all)
513 fun getFlovatarBackground(): UInt64?{
514 return self.background?.templateId
515 }
516
517 // This will allow to change the Background of the Flobot any time.
518 // It checks for the right category and series before executing.
519 access(all)
520 fun setFlovatarBackground(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?{
521 if(component.getCategory() != "background"){
522 panic("The component needs to be a background")
523 }
524 emit Updated(id: self.id)
525 let compNFT <- self.background <- component
526 return <-compNFT
527 }
528
529 // This will allow to remove the Background of the Flobot any time.
530 access(all)
531 fun removeFlovatarBackground(): @FlovatarComponent.NFT?{
532 emit Updated(id: self.id)
533 let compNFT <- self.background <- nil
534 return <-compNFT
535 }
536
537 // This function will return the full SVG of the Sportvatar. It will take the
538 // optional Background and the other Sportbit components from their
539 // original Template resources, while all the other unmutable components are
540 // taken from the Metadata directly.
541 access(all)
542 fun getSvg(): String{
543 let series = SportvatarTemplate.getSeries(id: self.series)
544 let layersArr: [String] = []
545 for k in (series!).layers.keys{
546 layersArr.append("")
547 }
548 var svg: String = (series!).svgPrefix
549 if let background = self.getFlovatarBackground(){
550 if let template = FlovatarComponentTemplate.getComponentTemplate(id: background){
551 svg = svg.concat(template.svg!)
552 }
553 }
554 for k in self.layers.keys{
555 if self.layers[k] != nil{
556 let layer = self.layers[k]!
557 if layer != nil{
558 let tempSvg = SportvatarTemplate.getTemplateSvg(id: layer!)
559 //svg = svg.concat(tempSvg!)
560 layersArr[k - UInt32(1)] = tempSvg!
561 }
562 }
563 }
564 for tempLayer in layersArr{
565 svg = svg.concat(tempLayer)
566 }
567 svg = svg.concat((series!).svgSuffix)
568 return svg
569 }
570
571 access(all)
572 view fun getViews(): [Type]{
573 return [
574 Type<MetadataViews.NFTCollectionData>(),
575 Type<MetadataViews.NFTCollectionDisplay>(),
576 Type<MetadataViews.Display>(),
577 Type<MetadataViews.Royalties>(),
578 Type<MetadataViews.Edition>(),
579 Type<MetadataViews.ExternalURL>(),
580 Type<MetadataViews.Serial>(),
581 Type<MetadataViews.Traits>()
582 ]
583 }
584
585 access(all)
586 fun resolveView(_ type: Type): AnyStruct?{
587 if type == Type<MetadataViews.ExternalURL>(){
588 return MetadataViews.ExternalURL("https://sportvatar.com/collectible/".concat(self.id.toString()))
589 }
590 if type == Type<MetadataViews.Royalties>(){
591 let royalties: [MetadataViews.Royalty] = []
592 var count: Int = 0
593 for royalty in self.royalties.royalty{
594 royalties.append(MetadataViews.Royalty(receiver: royalty.wallet, cut: royalty.cut, description: "Sportvatar Royalty ".concat(count.toString())))
595 count = count + Int(1)
596 }
597 return MetadataViews.Royalties(royalties)
598 }
599 if type == Type<MetadataViews.Serial>(){
600 return MetadataViews.Serial(self.id)
601 }
602 if type == Type<MetadataViews.Editions>(){
603 let series = self.getSeries()
604 var maxMintable: UInt64 = (series!).maxMintable
605 if maxMintable == UInt64(0){
606 maxMintable = UInt64(999999)
607 }
608 let editionInfo = MetadataViews.Edition(name: "Sportvatar Series ".concat(self.series.toString()), number: self.mint, max: maxMintable)
609 let editionList: [MetadataViews.Edition] = [editionInfo]
610 return MetadataViews.Editions(editionList)
611 }
612 if type == Type<MetadataViews.NFTCollectionDisplay>(){
613 let mediaSquare = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://images.sportvatar.com/logo.svg"), mediaType: "image/svg+xml")
614 let mediaBanner = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://images.sportvatar.com/logo-horizontal.svg"), mediaType: "image/svg+xml")
615 return MetadataViews.NFTCollectionDisplay(name: "Sportvatar Collectible", description: "Sportvatar is the next generation of composable and customizable Digital Collectibles", externalURL: MetadataViews.ExternalURL("https://sportvatar.com"), squareImage: mediaSquare, bannerImage: mediaBanner, socials:{ "discord": MetadataViews.ExternalURL("https://discord.gg/sportvatar"), "twitter": MetadataViews.ExternalURL("https://twitter.com/sportvatar"), "instagram": MetadataViews.ExternalURL("https://instagram.com/sportvatar_nft"), "tiktok": MetadataViews.ExternalURL("https://www.tiktok.com/@sportvatar")})
616 }
617 if type == Type<MetadataViews.Display>(){
618 return MetadataViews.Display(name: self.name == "" ? "Sportvatar #".concat(self.id.toString()) : self.name, description: self.description, thumbnail: MetadataViews.HTTPFile(url: "https://images.sportvatar.com/sportvatar/svg/".concat(self.id.toString()).concat(".svg")))
619 }
620 if type == Type<MetadataViews.Traits>(){
621 let traits: [MetadataViews.Trait] = []
622 let series = self.getSeries()
623 for k in self.layers.keys{
624 if self.layers[k] != nil{
625 let layer = (series!).layers[k]!
626 if self.layers[k] != nil{
627 let layerSelf = self.layers[k]!
628 if layer != nil{
629 let template = SportvatarTemplate.getTemplate(id: layerSelf!)
630 let trait = MetadataViews.Trait(name: (layer!).name, value: (template!).name, displayType: "String", rarity: MetadataViews.Rarity(score: nil, max: nil, description: (template!).rarity))
631 traits.append(trait)
632 }
633 }
634 }
635 }
636 return MetadataViews.Traits(traits)
637 }
638 if type == Type<MetadataViews.NFTCollectionData>(){
639 return MetadataViews.NFTCollectionData(storagePath: Sportvatar.CollectionStoragePath, publicPath: Sportvatar.CollectionPublicPath, publicCollection: Type<&Sportvatar.Collection>(), publicLinkedType: Type<&Sportvatar.Collection>(), createEmptyCollectionFunction: fun (): @{NonFungibleToken.Collection}{
640 return <-Sportvatar.createEmptyCollection(nftType: Type<@Sportvatar.Collection>())
641 })
642 }
643 return nil
644 }
645
646 access(all)
647 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
648 return <-create Collection()
649 }
650 }
651
652 // Standard NFT collectionPublic interface that can also borrowSportvatar as the correct type
653 access(all)
654 resource interface CollectionPublic{
655 access(all)
656 fun deposit(token: @{NonFungibleToken.NFT})
657
658 access(all)
659 fun getIDs(): [UInt64]
660
661 access(all)
662 view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
663
664 access(all)
665 fun borrowSportvatar(id: UInt64): &Sportvatar.NFT?{
666 // If the result isn't nil, the id of the returned reference
667 // should be the same as the argument to the function
668 post{
669 result == nil || result?.id == id:
670 "Cannot borrow Sportvatar Dust Collectible reference: The ID of the returned reference is incorrect"
671 }
672 }
673 }
674
675 // Main Collection to manage all the Sportvatar NFT
676 access(all)
677 resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.Collection, NonFungibleToken.CollectionPublic, ViewResolver.ResolverCollection{
678 // dictionary of NFT conforming tokens
679 // NFT is a resource type with an `UInt64` ID field
680 access(all)
681 var ownedNFTs: @{UInt64:{ NonFungibleToken.NFT}}
682
683 init(){
684 self.ownedNFTs <-{}
685 }
686
687 // withdraw removes an NFT from the collection and moves it to the caller
688 access(NonFungibleToken.Withdraw)
689 fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT}{
690 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
691 emit Withdraw(id: token.id, from: self.owner?.address)
692 return <-token
693 }
694
695 // deposit takes a NFT and adds it to the collections dictionary
696 // and adds the ID to the id array
697 access(all)
698 fun deposit(token: @{NonFungibleToken.NFT}){
699 let token <- token as! @Sportvatar.NFT
700 let id: UInt64 = token.id
701
702 // add the new token to the dictionary which removes the old one
703 let oldToken <- self.ownedNFTs[id] <- token
704 emit Deposit(id: id, to: self.owner?.address)
705 destroy oldToken
706 }
707
708 // getIDs returns an array of the IDs that are in the collection
709 access(all)
710 view fun getIDs(): [UInt64]{
711 return self.ownedNFTs.keys
712 }
713
714 // borrowNFT gets a reference to an NFT in the collection
715 // so that the caller can read its metadata and call its methods
716 access(all)
717 view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?{
718 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
719 }
720
721 // borrowSportvatar returns a borrowed reference to a Sportvatar
722 // so that the caller can read data and call methods from it.
723 access(all)
724 fun borrowSportvatar(id: UInt64): &Sportvatar.NFT?{
725 if self.ownedNFTs[id] != nil{
726 let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
727 let collectibleNFT = ref as! &Sportvatar.NFT
728 return collectibleNFT as &Sportvatar.NFT
729 } else{
730 return nil
731 }
732 }
733
734 // borrowSportvatarPrivate returns a borrowed reference to a Sportvatar using the Private interface
735 // so that the caller can read data and call methods from it, like setting the optional components.
736 access(all)
737 fun borrowSportvatarPrivate(id: UInt64): &{Sportvatar.Private}?{
738 if self.ownedNFTs[id] != nil{
739 let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
740 return ref as! &Sportvatar.NFT
741 } else{
742 return nil
743 }
744 }
745
746 access(all)
747 view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?{
748 pre{
749 self.ownedNFTs[id] != nil:
750 "NFT does not exist"
751 }
752 let nft = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
753 let collectibleNFT = nft as! &Sportvatar.NFT
754 return collectibleNFT as &{ViewResolver.Resolver}
755 }
756
757 access(all)
758 view fun getSupportedNFTTypes():{ Type: Bool}{
759 panic("implement me")
760 }
761
762 access(all)
763 view fun isSupportedNFTType(type: Type): Bool{
764 panic("implement me")
765 }
766
767 access(all)
768 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
769 return <-create Collection()
770 }
771 }
772
773 // public function that anyone can call to create a new empty collection
774 access(all)
775 fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection}{
776 return <-create Collection()
777 }
778
779
780
781
782
783 access(all)
784 view fun getContractViews(resourceType: Type?): [Type] {
785 return [
786 Type<MetadataViews.NFTCollectionData>(),
787 Type<MetadataViews.NFTCollectionDisplay>(),
788 Type<MetadataViews.EVMBridgedMetadata>()
789 ]
790 }
791
792
793 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
794 switch viewType {
795 case Type<MetadataViews.NFTCollectionData>():
796 let collectionData = MetadataViews.NFTCollectionData(
797 storagePath: self.CollectionStoragePath,
798 publicPath: self.CollectionPublicPath,
799 publicCollection: Type<&Sportvatar.Collection>(),
800 publicLinkedType: Type<&Sportvatar.Collection>(),
801 createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
802 return <-Sportvatar.createEmptyCollection(nftType: Type<@Sportvatar.NFT>())
803 })
804 )
805 return collectionData
806 case Type<MetadataViews.NFTCollectionDisplay>():
807 let media = MetadataViews.Media(
808 file: MetadataViews.HTTPFile(
809 url: "https://images.sportvatar.com/logo.svg"
810 ),
811 mediaType: "image/svg+xml"
812 )
813 let mediaBanner = MetadataViews.Media(
814 file: MetadataViews.HTTPFile(
815 url: "https://images.sportvatar.com/logo-horizontal.svg"
816 ),
817 mediaType: "image/svg+xml"
818 )
819 return MetadataViews.NFTCollectionDisplay(
820 name: "Sportvatar Collection",
821 description: "Sportvatar is the next generation of composable and customizable Digital Collectibles",
822 externalURL: MetadataViews.ExternalURL("https://sportvatar.com"),
823 squareImage: media,
824 bannerImage: mediaBanner,
825 socials: {
826 "twitter": MetadataViews.ExternalURL("https://x.com/sportvatar"),
827 "discord": MetadataViews.ExternalURL("https://discord.gg/sportvatar"),
828 "instagram": MetadataViews.ExternalURL("https://instagram.com/sportvatar"),
829 "tiktok": MetadataViews.ExternalURL("https://www.tiktok.com/@sportvatar")
830 }
831 )
832 case Type<MetadataViews.EVMBridgedMetadata>():
833 // Implementing this view gives the project control over how the bridged NFT is represented as an ERC721
834 // when bridged to EVM on Flow via the public infrastructure bridge.
835
836 // Compose the contract-level URI. In this case, the contract metadata is located on some HTTP host,
837 // but it could be IPFS, S3, a data URL containing the JSON directly, etc.
838 return MetadataViews.EVMBridgedMetadata(
839 name: "Sportvatar",
840 symbol: "SPTV",
841 uri: MetadataViews.URI(
842 baseURI: nil, // setting baseURI as nil sets the given value as the uri field value
843 value: "https://sportvatar.com"
844 )
845 )
846 }
847 return nil
848 }
849
850
851
852
853
854 // This struct is used to send a data representation of the Sportvatar Dust Collectibles
855 // when retrieved using the contract helper methods outside the collection.
856 access(all)
857 struct SportvatarData{
858 access(all)
859 let id: UInt64
860
861 access(all)
862 let mint: UInt64
863
864 access(all)
865 let series: UInt64
866
867 access(all)
868 let name: String
869
870 access(all)
871 let rarity: String
872
873 access(all)
874 let svg: String?
875
876 access(all)
877 let combination: String
878
879 access(all)
880 let creatorAddress: Address
881
882 access(all)
883 let layers:{ UInt32: UInt64?}
884
885 access(all)
886 let bio:{ String: String}
887
888 access(all)
889 let metadata:{ String: String}
890
891 access(all)
892 let stats:{ String: UInt32}
893
894 init(id: UInt64, mint: UInt64, series: UInt64, name: String, rarity: String, svg: String?, combination: String, creatorAddress: Address, layers:{ UInt32: UInt64?}, bio:{ String: String}, metadata:{ String: String}, stats:{ String: UInt32}){
895 self.id = id
896 self.mint = mint
897 self.series = series
898 self.name = name
899 self.rarity = rarity
900 self.svg = svg
901 self.combination = combination
902 self.creatorAddress = creatorAddress
903 self.layers = layers
904 self.bio = bio
905 self.metadata = metadata
906 self.stats = stats
907 }
908 }
909
910 // This function will look for a specific Sportvatar on a user account and return a SportvatarData if found
911 access(all)
912 fun getSportvatar(address: Address, sportvatarId: UInt64): SportvatarData?{
913 let account = getAccount(address)
914 if let collectibleCollection = account.capabilities.borrow<&Sportvatar.Collection>(Sportvatar.CollectionPublicPath){
915 if let collectible = collectibleCollection.borrowSportvatar(id: sportvatarId){
916 return SportvatarData(id: sportvatarId, mint: (collectible!).mint, series: (collectible!).series, name: (collectible!).getName(), rarity: (collectible!).rarity, svg: (collectible!).getSvg(), combination: (collectible!).combination, creatorAddress: (collectible!).creatorAddress, layers: (collectible!).getLayers(), bio: (collectible!).getBio(), metadata: (collectible!).getMetadata(), stats: (collectible!).getStats())
917 }
918 }
919 return nil
920 }
921
922 // This function will return all Sportvatars on a user account and return an array of SportvatarData
923 access(all)
924 fun getSportvatars(address: Address): [SportvatarData]{
925 var sportvatarData: [SportvatarData] = []
926 let account = getAccount(address)
927 if let collectibleCollection = account.capabilities.borrow<&Sportvatar.Collection>(Sportvatar.CollectionPublicPath){
928 for id in collectibleCollection.getIDs(){
929 if let collectible = collectibleCollection.borrowSportvatar(id: id){
930 sportvatarData.append(SportvatarData(id: id, mint: (collectible!).mint, series: (collectible!).series, name: (collectible!).getName(), rarity: (collectible!).rarity, svg: nil, combination: (collectible!).combination, creatorAddress: (collectible!).creatorAddress, layers: (collectible!).getLayers(), bio: (collectible!).getBio(), metadata: (collectible!).getMetadata(), stats: (collectible!).getStats()))
931 }
932 }
933 }
934 return sportvatarData
935 }
936
937 // This returns all the previously minted combinations, so that duplicates won't be allowed
938 access(all)
939 fun getMintedCombinations(): [String]{
940 return Sportvatar.mintedCombinations.keys
941 }
942
943 // This returns all the previously minted names, so that duplicates won't be allowed
944 access(all)
945 fun getMintedNames(): [String]{
946 return Sportvatar.mintedNames.keys
947 }
948
949 // This function will add a minted combination to the array
950 access(account)
951 fun addMintedCombination(combination: String){
952 Sportvatar.mintedCombinations.insert(key: combination, true)
953 }
954
955 // This function will add a new name to the array
956 access(account)
957 fun addMintedName(name: String){
958 Sportvatar.mintedNames.insert(key: name, true)
959 }
960
961 access(all)
962 fun getCoreLayers(series: UInt64, layers:{ UInt32: UInt64?}):{ UInt32: UInt64}{
963 let coreLayers:{ UInt32: UInt64} ={}
964 for k in layers.keys{
965 if !SportvatarTemplate.isCollectibleLayerAccessory(layer: k, series: series){
966 let templateId = layers[k]!
967 let template = SportvatarTemplate.getTemplate(id: templateId!)!
968 if template.series != series{
969 panic("Template belonging to the wrong Dust Collectible Series")
970 }
971 if template.layer != k{
972 panic("Template belonging to the wrong Layer")
973 }
974 coreLayers[k] = templateId!
975 }
976 }
977 return coreLayers
978 }
979
980 // This helper function will generate a string from a list of components,
981 // to be used as a sort of barcode to keep the inventory of the minted
982 // Sportvatars and to avoid duplicates
983 access(all)
984 fun getCombinationString(series: UInt64, layers:{ UInt32: UInt64}): String{
985 var combination: String = "S".concat(series.toString())
986 var i: UInt32 = UInt32(2)
987 while i < UInt32(9){
988 let layer = layers[i]!
989 combination = combination.concat("-L").concat(i.toString()).concat("_").concat(layer.toString())
990 i = i + UInt32(1)
991 }
992 return combination
993 }
994
995 // This function will get a list of component IDs and will check if the
996 // generated string is unique or if someone already used it before.
997 access(all)
998 fun checkCombinationAvailable(series: UInt64, layers:{ UInt32: UInt64}): Bool{
999 let combinationString = Sportvatar.getCombinationString(series: series, layers: layers)
1000 return !Sportvatar.mintedCombinations.containsKey(combinationString)
1001 }
1002
1003 // This will check if a specific Name has already been taken
1004 // and assigned to some Sportvatar
1005 access(all)
1006 fun checkNameAvailable(name: String): Bool{
1007 return name.length > 2 && name.length < 20 && !Sportvatar.mintedNames.containsKey(name)
1008 }
1009
1010 // This is a public function that anyone can call to generate a new Sportvatar
1011 // A list of components resources needs to be passed to executed.
1012 // It will check first for uniqueness of the combination + name and will then
1013 // generate the Sportvatar and burn all the passed components.
1014 // The Flame NFT will entitle to use any common basic component (body, hair, etc.)
1015 // In order to use special rare components a boost of the same rarity will be needed
1016 // for each component used
1017 access(all)
1018 fun createSportvatar(sportflame: @Sportbit.NFT, series: UInt64, layers: [UInt32], templateIds: [UInt64?], sportbits: @[Sportbit.NFT?], address: Address): @Sportvatar.NFT{
1019 let seriesData = SportvatarTemplate.getSeries(id: series)
1020 if seriesData == nil{
1021 panic("Dust Collectible Series not found!")
1022 }
1023 if (seriesData!).layers.length != layers.length{
1024 panic("The amount of layers is not matching!")
1025 }
1026 if templateIds.length != layers.length{
1027 panic("The amount of layers and templates is not matching!")
1028 }
1029 let mintedCollectibles = SportvatarTemplate.getTotalMintedCollectibles(series: series)
1030 if mintedCollectibles != nil{
1031 if (seriesData!).maxMintable > UInt64(0) && mintedCollectibles! >= (seriesData!).maxMintable{
1032 panic("Reached the maximum mint number for this Series!")
1033 }
1034 }
1035 let templates: [SportvatarTemplate.TemplateData] = []
1036 let coreLayers:{ UInt32: UInt64} ={}
1037 let fullLayers:{ UInt32: UInt64?} ={}
1038 let flameRarity: String = sportflame.getRarity()
1039 let metadata:{ String: String} ={}
1040 let stats:{ String: UInt32} ={}
1041 if sportflame.getLayer() != UInt32(0){
1042 panic("The Sport Flame belongs to the wrong category")
1043 }
1044 if sportflame.getSeries() != series{
1045 panic("The Sport Flame doesn't belong to the correct series")
1046 }
1047 var i: UInt32 = UInt32(0)
1048 while i < UInt32(layers.length){
1049 let layerId: UInt32 = layers[i]!
1050 let templateId: UInt64? = templateIds[i] ?? nil
1051 if !SportvatarTemplate.isCollectibleLayerAccessory(layer: layerId, series: series){
1052 if templateId == nil{
1053 panic("Core Layer missing ".concat(layerId.toString()).concat(" - ").concat(i.toString()).concat("/").concat(layers.length.toString()))
1054 }
1055 let template = SportvatarTemplate.getTemplate(id: templateId!)!
1056 if template.series != series{
1057 panic("Template belonging to the wrong Series")
1058 }
1059 if template.layer != layerId{
1060 panic("Template belonging to the wrong Layer")
1061 }
1062 if template.name == "clothing"{
1063 metadata["sport"] = template.sport
1064 }
1065 let templateRarity: String = template.rarity
1066 var checkRarity: Bool = false
1067 if flameRarity == "common"{
1068 if templateRarity != "common"{
1069 checkRarity = true
1070 }
1071 } else if flameRarity == "rare"{
1072 if templateRarity == "epic" || templateRarity == "legendary"{
1073 checkRarity = true
1074 }
1075 } else if flameRarity == "epic"{
1076 if templateRarity == "legendary"{
1077 checkRarity = true
1078 }
1079 }
1080 if checkRarity{
1081 panic("Sport Flame does not belong to the correct Rarity")
1082 }
1083 let totalMintedComponents: UInt64 = SportvatarTemplate.getTotalMintedComponents(id: template.id)!
1084 // Makes sure that the original minting limit set for each Template has not been reached
1085 if template.maxMintableComponents > UInt64(0) && totalMintedComponents >= template.maxMintableComponents{
1086 panic("Reached maximum mintable count for this trait")
1087 }
1088 coreLayers[layerId] = template.id
1089 fullLayers[layerId] = template.id
1090 templates.append(template)
1091 SportvatarTemplate.increaseTotalMintedComponents(id: template.id)
1092 SportvatarTemplate.setLastComponentMintedAt(id: template.id, value: getCurrentBlock().timestamp)
1093 } else{
1094 fullLayers[layerId] = nil
1095 }
1096 i = i + UInt32(1)
1097 }
1098
1099 // Generates the combination string to check for uniqueness.
1100 // This is like a barcode that defines exactly which components were used
1101 // to create the Sportvatar
1102 let combinationString = Sportvatar.getCombinationString(series: series, layers: coreLayers)
1103
1104 // Makes sure that the combination is available and not taken already
1105 if Sportvatar.mintedCombinations.containsKey(combinationString) == true{
1106 panic("This combination has already been taken")
1107 }
1108
1109 //Generate random stats based on the rarity level
1110 var minValue: UInt64 = 5
1111 var maxValue: UInt64 = 20
1112 if flameRarity == "rare"{
1113 minValue = 20
1114 maxValue = 30
1115 } else if flameRarity == "epic"{
1116 minValue = 30
1117 maxValue = 40
1118 } else if flameRarity == "legendary"{
1119 minValue = 40
1120 maxValue = 51
1121 }
1122 var tempRand: Int64 = 0
1123 var tempOverall: UInt64 = RandomInt(uuid: revertibleRandom<UInt64>(), field: "overall", minValue: minValue, maxValue: maxValue).getValue()
1124 var tempMax: Int64 = 0
1125 var tempMin: Int64 = 5
1126 var pointsLeft: Int64 = Int64(tempOverall)
1127
1128 //stats["overall"] = UInt32(tempOverall)
1129 tempMax = pointsLeft - Int64(4)
1130 tempMax = tempMax > Int64(10) ? Int64(10) : tempMax
1131 tempMin = pointsLeft - Int64(40)
1132 tempMin = tempMin < Int64(1) ? Int64(1) : tempMin
1133 tempRand = tempMin == tempMax ? tempMin : Int64(RandomInt(uuid: revertibleRandom<UInt64>(), field: "mental strength", minValue: UInt64(tempMin), maxValue: UInt64(tempMax) + UInt64(1)).getValue())
1134 stats["mental strength"] = UInt32(tempRand)
1135 pointsLeft = pointsLeft - tempRand
1136 tempMax = pointsLeft - Int64(3)
1137 tempMax = tempMax > Int64(10) ? Int64(10) : tempMax
1138 tempMin = pointsLeft - Int64(30)
1139 tempMin = tempMin < Int64(1) ? Int64(1) : tempMin
1140 tempRand = tempMin == tempMax ? tempMin : Int64(RandomInt(uuid: revertibleRandom<UInt64>(), field: "speed", minValue: UInt64(tempMin), maxValue: UInt64(tempMax) + UInt64(1)).getValue())
1141 stats["speed"] = UInt32(tempRand)
1142 pointsLeft = pointsLeft - tempRand
1143 tempMax = pointsLeft - Int64(2)
1144 tempMax = tempMax > Int64(10) ? Int64(10) : tempMax
1145 tempMin = pointsLeft - Int64(20)
1146 tempMin = tempMin < Int64(1) ? Int64(1) : tempMin
1147 tempRand = tempMin == tempMax ? tempMin : Int64(RandomInt(uuid: revertibleRandom<UInt64>(), field: "power", minValue: UInt64(tempMin), maxValue: UInt64(tempMax) + UInt64(1)).getValue())
1148 stats["power"] = UInt32(tempRand)
1149 pointsLeft = pointsLeft - tempRand
1150 tempMax = pointsLeft - Int64(1)
1151 tempMax = tempMax > Int64(10) ? Int64(10) : tempMax
1152 tempMin = pointsLeft - Int64(10)
1153 tempMin = tempMin < Int64(1) ? Int64(1) : tempMin
1154 tempRand = tempMin == tempMax ? tempMin : Int64(RandomInt(uuid: revertibleRandom<UInt64>(), field: "technique", minValue: UInt64(tempMin), maxValue: UInt64(tempMax) + UInt64(1)).getValue())
1155 stats["technique"] = UInt32(tempRand)
1156 pointsLeft = pointsLeft - tempRand
1157 stats["endurance"] = UInt32(pointsLeft)
1158 if pointsLeft < Int64(1){
1159 panic("Error distributing stat points to Sportvatar")
1160 }
1161 let royalties: [Royalty] = []
1162 royalties.append(Royalty(wallet: self.account.capabilities.get<&FlowToken.Vault>(/public/flowTokenReceiver), cut: Sportvatar.getMarketplaceCut(), type: RoyaltyType.percentage))
1163
1164 // Mint the new Sportvatar NFT by passing the metadata to it
1165 var newNFT <- create NFT(series: series, layers: fullLayers, metadata: metadata, stats: stats, creatorAddress: address, royalties: Royalties(royalty: royalties), rarity: flameRarity)
1166
1167 // Adds the combination to the arrays to remember it
1168 Sportvatar.addMintedCombination(combination: combinationString)
1169
1170 // Emits the Created event to notify about its existence
1171 emit Created(id: newNFT.id, mint: newNFT.mint, series: newNFT.series, address: address)
1172 i = UInt32(0)
1173 let sportbitLayers:{ UInt32: UInt64} ={}
1174 while i < UInt32(sportbits.length){
1175 let sportbitTemp <- sportbits[i] <- nil
1176 if sportbitTemp != nil{
1177 let tempLayer: UInt32 = sportbitTemp?.getLayer()!
1178 if sportbitLayers[tempLayer] == nil{
1179 sportbitLayers[tempLayer] = sportbitTemp?.getTemplate()?.id
1180 let temp <- newNFT.setSportbit(layer: tempLayer, sportbit: <-sportbitTemp!)
1181 if temp != nil{
1182 panic("Sportbit already set and preventing to be destroyed")
1183 }
1184 destroy temp
1185 } else{
1186 destroy sportbitTemp
1187 panic("Sending multiple Sportbits for the same Layer")
1188 }
1189 } else{
1190 destroy sportbitTemp
1191 }
1192 }
1193 destroy sportbits
1194 destroy sportflame
1195 return <-newNFT
1196 }
1197
1198 // These functions will return the current Royalty cuts for
1199 // both the Creator and the Marketplace.
1200 access(all)
1201 fun getRoyaltyCut(): UFix64{
1202 return self.royaltyCut
1203 }
1204
1205 access(all)
1206 fun getMarketplaceCut(): UFix64{
1207 return self.marketplaceCut
1208 }
1209
1210 // Only Admins will be able to call the set functions to
1211 // manage Royalties and Marketplace cuts.
1212 access(account)
1213 fun setRoyaltyCut(value: UFix64){
1214 self.royaltyCut = value
1215 }
1216
1217 access(account)
1218 fun setMarketplaceCut(value: UFix64){
1219 self.marketplaceCut = value
1220 }
1221
1222 // This is the main Admin resource that will allow the owner
1223 // to generate new Templates, Components and Packs
1224 access(all)
1225 resource Admin{
1226
1227 //This will create a new SportvatarTemplate that
1228 // contains all the SVG and basic informations to represent
1229 // a specific part of the Sportvatar (body, hair, eyes, mouth, etc.)
1230 // More info in the SportvatarTemplate.cdc file
1231 access(all)
1232 fun createSeries(name: String, description: String, svgPrefix: String, svgSuffix: String, layers:{ UInt32: SportvatarTemplate.Layer}, colors:{ UInt32: String}, metadata:{ String: String}, maxMintable: UInt64): @SportvatarTemplate.Series{
1233 return <-SportvatarTemplate.createSeries(name: name, description: description, svgPrefix: svgPrefix, svgSuffix: svgSuffix, layers: layers, colors: colors, metadata: metadata, maxMintable: maxMintable)
1234 }
1235
1236 //This will create a new SportvatarTemplate that
1237 // contains all the SVG and basic informations to represent
1238 // a specific part of the Sportvatar (body, hair, eyes, mouth, etc.)
1239 // More info in the SportvatarTemplate.cdc file
1240 access(all)
1241 fun createTemplate(name: String, description: String, series: UInt64, layer: UInt32, metadata:{ String: String}, rarity: String, sport: String, svg: String, maxMintableComponents: UInt64): @SportvatarTemplate.Template{
1242 return <-SportvatarTemplate.createTemplate(name: name, description: description, series: series, layer: layer, metadata: metadata, rarity: rarity, sport: sport, svg: svg, maxMintableComponents: maxMintableComponents)
1243 }
1244
1245 //This will mint a new Component based from a selected Template
1246 access(all)
1247 fun createSportbit(templateId: UInt64): @Sportbit.NFT{
1248 return <-Sportbit.createSportbit(templateId: templateId)
1249 }
1250
1251 //This will mint Components in batch and return a Collection instead of the single NFT
1252 access(all)
1253 fun batchCreateSportbits(templateId: UInt64, quantity: UInt64): @Sportbit.Collection{
1254 return <-Sportbit.batchCreateSportbits(templateId: templateId, quantity: quantity)
1255 }
1256
1257 // This function will generate a new Pack containing a set of components.
1258 // A random string is passed to manage permissions for the
1259 // purchase of it (more info on SportvatarPack.cdc).
1260 // Finally the sale price is set as well.
1261 access(all)
1262 fun createPack(components: @[Sportbit.NFT], randomString: String, price: UFix64, flameCount: UInt32, series: UInt32, name: String): @SportvatarPack.Pack{
1263 return <-SportvatarPack.createPack(components: <-components, randomString: randomString, price: price, flameCount: flameCount, series: series, name: name)
1264 }
1265
1266 // With this function you can generate a new Admin resource
1267 // and pass it to another user if needed
1268 access(all)
1269 fun createNewAdmin(): @Admin{
1270 return <-create Admin()
1271 }
1272
1273 // Helper functions to update the Royalty cut
1274 access(all)
1275 fun setRoyaltyCut(value: UFix64){
1276 Sportvatar.setRoyaltyCut(value: value)
1277 }
1278
1279 // Helper functions to update the Marketplace cut
1280 access(all)
1281 fun setMarketplaceCut(value: UFix64){
1282 Sportvatar.setMarketplaceCut(value: value)
1283 }
1284 }
1285
1286 init(){
1287 self.CollectionPublicPath = /public/SportvatarCollection
1288 self.CollectionStoragePath = /storage/SportvatarCollection
1289 self.AdminStoragePath = /storage/SportvatarAdmin
1290
1291 // Initialize the total supply
1292 self.totalSupply = UInt64(0)
1293 self.mintedCombinations ={}
1294 self.mintedNames ={}
1295
1296 // Set the default Royalty and Marketplace cuts
1297 self.royaltyCut = 0.01
1298 self.marketplaceCut = 0.05
1299 self.account.storage.save<@{NonFungibleToken.Collection}>(<-Sportvatar.createEmptyCollection(nftType: Type<@Sportvatar.Collection>()), to: Sportvatar.CollectionStoragePath)
1300 var capability_1 = self.account.capabilities.storage.issue<&Sportvatar.Collection>(Sportvatar.CollectionStoragePath)
1301 self.account.capabilities.publish(capability_1, at: Sportvatar.CollectionPublicPath)
1302
1303 // Put the Admin resource in storage
1304 self.account.storage.save<@Admin>(<-create Admin(), to: self.AdminStoragePath)
1305 emit ContractInitialized()
1306 }
1307}
1308