Smart Contract
Flovatar
A.921ea449dffec68a.Flovatar
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3import FlowToken from 0x1654653399040a61
4import FlovatarComponentTemplate from 0x921ea449dffec68a
5import FlovatarComponent from 0x921ea449dffec68a
6import FlovatarPack from 0x921ea449dffec68a
7import MetadataViews from 0x1d7e57aa55817448
8import FlovatarDustToken from 0x921ea449dffec68a
9import ViewResolver from 0x1d7e57aa55817448
10
11/*
12
13 The contract that defines the Flovatar NFT and a Collection to manage them
14
15Base components that will be used to generate the unique combination of the Flovatar
16'body', 'hair', 'facialhair', 'eyes', 'nose', 'mouth', 'clothing'
17
18Extra components that can be added in a second moment
19'accessory', 'hat', eyeglass', 'background'
20
21
22This contract contains also the Admin resource that can be used to manage and generate all the other ones (Components, Templates, Packs).
23
24 */
25
26access(all)
27contract Flovatar: NonFungibleToken{
28 access(all)
29 let CollectionStoragePath: StoragePath
30
31 access(all)
32 let CollectionPublicPath: PublicPath
33
34 access(all)
35 let AdminStoragePath: StoragePath
36
37 // These will be used in the Marketplace to pay out
38 // royalties to the creator and to the marketplace
39 access(account)
40 var royaltyCut: UFix64
41
42 access(account)
43 var marketplaceCut: UFix64
44
45 // Here we keep track of all the Flovatar unique combinations and names
46 // that people will generate to make sure that there are no duplicates
47 access(all)
48 var totalSupply: UInt64
49
50 access(contract)
51 let mintedCombinations:{ String: Bool}
52
53 access(contract)
54 let mintedNames:{ String: Bool}
55
56 access(all)
57 event ContractInitialized()
58
59 access(all)
60 event Withdraw(id: UInt64, from: Address?)
61
62 access(all)
63 event Deposit(id: UInt64, to: Address?)
64
65 access(all)
66 event Created(id: UInt64, metadata: Metadata)
67
68 access(all)
69 event Updated(id: UInt64)
70
71 access(all)
72 event NameSet(id: UInt64, name: String)
73
74 access(all)
75 event PositionChanged(id: UInt64, position: String)
76
77 access(all)
78 event StoryAdded(id: UInt64, story: String)
79
80 access(all)
81 event Unlocked3DFile(id: UInt64)
82
83 access(all)
84 event UnlockedChat(id: UInt64)
85
86 access(all)
87 struct Royalties{
88 access(all)
89 let royalty: [Royalty]
90
91 init(royalty: [Royalty]){
92 self.royalty = royalty
93 }
94 }
95
96 access(all)
97 enum RoyaltyType: UInt8{
98 access(all)
99 case fixed
100
101 access(all)
102 case percentage
103 }
104
105 access(all)
106 struct Royalty{
107 access(all)
108 let wallet: Capability<&{FungibleToken.Receiver}>
109
110 access(all)
111 let cut: UFix64
112
113 //can be percentage
114 access(all)
115 let type: RoyaltyType
116
117 init(wallet: Capability<&{FungibleToken.Receiver}>, cut: UFix64, type: RoyaltyType){
118 self.wallet = wallet
119 self.cut = cut
120 self.type = type
121 }
122 }
123
124 // This Metadata struct contains all the most important informations about the Flovatar
125 access(all)
126 struct Metadata{
127 access(all)
128 let mint: UInt64
129
130 access(all)
131 let series: UInt32
132
133 access(all)
134 let svg: String
135
136 access(all)
137 let combination: String
138
139 access(all)
140 let creatorAddress: Address
141
142 access(self)
143 let components:{ String: UInt64}
144
145 access(all)
146 let rareCount: UInt8
147
148 access(all)
149 let epicCount: UInt8
150
151 access(all)
152 let legendaryCount: UInt8
153
154 init(mint: UInt64, series: UInt32, svg: String, combination: String, creatorAddress: Address, components:{ String: UInt64}, rareCount: UInt8, epicCount: UInt8, legendaryCount: UInt8){
155 self.mint = mint
156 self.series = series
157 self.svg = svg
158 self.combination = combination
159 self.creatorAddress = creatorAddress
160 self.components = components
161 self.rareCount = rareCount
162 self.epicCount = epicCount
163 self.legendaryCount = legendaryCount
164 }
165
166 access(all)
167 fun getComponents():{ String: UInt64}{
168 return self.components
169 }
170 }
171
172 access(all) entitlement PrivateEnt
173
174 // The public interface can show metadata and the content for the Flovatar.
175 // In addition to it, it provides methods to access the additional optional
176 // components (accessory, hat, eyeglasses, background) for everyone.
177 access(all)
178 resource interface Public{
179 access(all)
180 let id: UInt64
181
182 access(contract)
183 let metadata: Metadata
184
185 access(contract)
186 let royalties: Royalties
187
188 // these three are added because I think they will be in the standard. At least Dieter thinks it will be needed
189 access(contract)
190 var name: String
191
192 access(all)
193 let description: String
194
195 access(all)
196 let schema: String?
197
198 access(all)
199 fun getName(): String
200
201 access(all)
202 fun getAccessory(): UInt64?
203
204 access(all)
205 fun getHat(): UInt64?
206
207 access(all)
208 fun getEyeglasses(): UInt64?
209
210 access(all)
211 fun getBackground(): UInt64?
212
213 access(all)
214 fun getSvg(): String
215
216 access(all)
217 fun getMetadata(): Metadata
218
219 access(all)
220 fun getRoyalties(): Royalties
221
222 access(all)
223 fun getBio():{ String: String}
224
225 access(all)
226 fun getRarityScore(): UFix64
227 }
228
229 //The private interface can update the Accessory, Hat, Eyeglasses and Background
230 //for the Flovatar and is accessible only to the owner of the NFT
231 access(all)
232 resource interface Private{
233 access(Flovatar.PrivateEnt)
234 fun setName(name: String, vault: @{FungibleToken.Vault}): String
235
236 access(Flovatar.PrivateEnt)
237 fun addStory(text: String, vault: @{FungibleToken.Vault}): String
238
239 access(Flovatar.PrivateEnt)
240 fun unlock3DFile(vault: @{FungibleToken.Vault})
241
242 access(Flovatar.PrivateEnt)
243 fun unlockChat(vault: @{FungibleToken.Vault})
244
245 access(Flovatar.PrivateEnt)
246 fun setPosition(latitude: Fix64, longitude: Fix64, vault: @{FungibleToken.Vault}): String
247
248 access(Flovatar.PrivateEnt)
249 fun setAccessory(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?
250
251 access(Flovatar.PrivateEnt)
252 fun setHat(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?
253
254 access(Flovatar.PrivateEnt)
255 fun setEyeglasses(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?
256
257 access(Flovatar.PrivateEnt)
258 fun setBackground(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?
259
260 access(Flovatar.PrivateEnt)
261 fun removeAccessory(): @FlovatarComponent.NFT?
262
263 access(Flovatar.PrivateEnt)
264 fun removeHat(): @FlovatarComponent.NFT?
265
266 access(Flovatar.PrivateEnt)
267 fun removeEyeglasses(): @FlovatarComponent.NFT?
268
269 access(Flovatar.PrivateEnt)
270 fun removeBackground(): @FlovatarComponent.NFT?
271 }
272
273 //The NFT resource that implements both Private and Public interfaces
274 access(all)
275 resource NFT: NonFungibleToken.NFT, Public, Private{
276 access(all)
277 let id: UInt64
278
279 access(contract)
280 let metadata: Metadata
281
282 access(contract)
283 let royalties: Royalties
284
285 access(contract)
286 var accessory: @FlovatarComponent.NFT?
287
288 access(contract)
289 var hat: @FlovatarComponent.NFT?
290
291 access(contract)
292 var eyeglasses: @FlovatarComponent.NFT?
293
294 access(contract)
295 var background: @FlovatarComponent.NFT?
296
297 access(contract)
298 var name: String
299
300 access(all)
301 let description: String
302
303 access(all)
304 let schema: String?
305
306 access(self)
307 let bio:{ String: String}
308
309 init(metadata: Metadata, royalties: Royalties){
310 Flovatar.totalSupply = Flovatar.totalSupply + UInt64(1)
311 self.id = Flovatar.totalSupply
312 self.metadata = metadata
313 self.royalties = royalties
314 self.accessory <- nil
315 self.hat <- nil
316 self.eyeglasses <- nil
317 self.background <- nil
318 self.schema = nil
319 self.name = ""
320 self.description = ""
321 self.bio ={}
322 }
323
324 access(all)
325 fun getID(): UInt64{
326 return self.id
327 }
328
329 access(all)
330 fun getMetadata(): Metadata{
331 return self.metadata
332 }
333
334 access(all)
335 fun getRoyalties(): Royalties{
336 return self.royalties
337 }
338
339 access(all)
340 fun getBio():{ String: String}{
341 return self.bio
342 }
343
344 access(all)
345 fun getName(): String{
346 return self.name
347 }
348
349 // This will allow to change the Name of the Flovatar only once.
350 // It checks for the current name is empty, otherwise it will throw an error.
351 // $DUST vault must contain 100 tokens that will be burned in the process
352 access(Flovatar.PrivateEnt)
353 fun setName(name: String, vault: @{FungibleToken.Vault}): String{
354 pre{
355 // TODO: Make sure that the text of the name is sanitized
356 //and that bad words are not accepted?
357 name.length > 2:
358 "The name is too short"
359 name.length < 32:
360 "The name is too long"
361 self.name == "":
362 "The name has already been set"
363 vault.balance == 100.0:
364 "The amount of $DUST is not correct"
365 vault.isInstance(Type<@FlovatarDustToken.Vault>()):
366 "Vault not of the right Token Type"
367 }
368
369 // Makes sure that the name is available and not taken already
370 if Flovatar.checkNameAvailable(name: name) == false{
371 panic("This name has already been taken")
372 }
373 destroy vault
374 self.name = name
375
376 // Adds the name to the array to remember it
377 Flovatar.addMintedName(name: name)
378 emit NameSet(id: self.id, name: name)
379 return self.name
380 }
381
382 // This will allow to add a text Story to the Flovatar Bio.
383 // The String will be concatenated each time.
384 // There is a limit of 300 characters per story but there is no limit in the full concatenated story length
385 // $DUST vault must contain 50 tokens that will be burned in the process
386 access(Flovatar.PrivateEnt)
387 fun addStory(text: String, vault: @{FungibleToken.Vault}): String{
388 pre{
389 // TODO: Make sure that the text of the name is sanitized
390 //and that bad words are not accepted?
391 text.length > 0:
392 "The text is too short"
393 text.length <= 300:
394 "The text is too long"
395 vault.balance == 50.0:
396 "The amount of $DUST is not correct"
397 vault.isInstance(Type<@FlovatarDustToken.Vault>()):
398 "Vault not of the right Token Type"
399 }
400 destroy vault
401 let currentStory: String = self.bio["story"] ?? ""
402 let story: String = currentStory.concat("\n").concat(text)
403 self.bio.insert(key: "story", story)
404 emit StoryAdded(id: self.id, story: story)
405 return story
406 }
407
408 // This will unlock the 3D files for a Flovatar.
409 // $DUST vault must contain 150 tokens that will be burned in the process
410 access(Flovatar.PrivateEnt)
411 fun unlock3DFile(vault: @{FungibleToken.Vault}){
412 pre{
413 self.bio["3d"] == nil:
414 "The 3D File has been already unlocked"
415 vault.balance == 150.0:
416 "The amount of $DUST is not correct"
417 vault.isInstance(Type<@FlovatarDustToken.Vault>()):
418 "Vault not of the right Token Type"
419 }
420 destroy vault
421 self.bio.insert(key: "3d", "true")
422 emit Unlocked3DFile(id: self.id)
423 }
424
425 // This will enable the chat customization for a Flovatar.
426 // $DUST vault must contain 5000 tokens that will be burned in the process
427 access(Flovatar.PrivateEnt)
428 fun unlockChat(vault: @{FungibleToken.Vault}){
429 pre{
430 self.bio["chat"] == nil:
431 "The Chat has been already unlocked"
432 vault.balance == 5000.0:
433 "The amount of $DUST is not correct"
434 vault.isInstance(Type<@FlovatarDustToken.Vault>()):
435 "Vault not of the right Token Type"
436 }
437 destroy vault
438 self.bio.insert(key: "chat", "true")
439 emit UnlockedChat(id: self.id)
440 }
441
442 // This will allow to set the GPS location of a Flovatar
443 // It can be run multiple times and each time it will override the previous state
444 // $DUST vault must contain 10 tokens that will be burned in the process
445 access(Flovatar.PrivateEnt)
446 fun setPosition(latitude: Fix64, longitude: Fix64, vault: @{FungibleToken.Vault}): String{
447 pre{
448 latitude >= -90.0:
449 "The latitude is out of range"
450 latitude <= 90.0:
451 "The latitude is out of range"
452 longitude >= -180.0:
453 "The longitude is out of range"
454 longitude <= 180.0:
455 "The longitude is out of range"
456 vault.balance == 10.0:
457 "The amount of $DUST is not correct"
458 vault.isInstance(Type<@FlovatarDustToken.Vault>()):
459 "Vault not of the right Token Type"
460 }
461 destroy vault
462 let position: String = latitude.toString().concat(",").concat(longitude.toString())
463 self.bio.insert(key: "position", position)
464 emit PositionChanged(id: self.id, position: position)
465 return position
466 }
467
468 access(all)
469 fun getAccessory(): UInt64?{
470 return self.accessory?.templateId
471 }
472
473 // This will allow to change the Accessory of the Flovatar any time.
474 // It checks for the right category and series before executing.
475 access(Flovatar.PrivateEnt)
476 fun setAccessory(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?{
477 if(component.getCategory() != "accessory") {
478 panic("The component needs to be an accessory")
479 }
480 if(component.getSeries() != self.metadata.series) {
481 panic("The accessory belongs to a different series")
482 }
483 emit Updated(id: self.id)
484 let compNFT <- self.accessory <- component
485 return <-compNFT
486 }
487
488 // This will allow to remove the Accessory of the Flovatar any time.
489 access(Flovatar.PrivateEnt)
490 fun removeAccessory(): @FlovatarComponent.NFT?{
491 emit Updated(id: self.id)
492 let compNFT <- self.accessory <- nil
493 return <-compNFT
494 }
495
496 access(all)
497 fun getHat(): UInt64?{
498 return self.hat?.templateId
499 }
500
501 // This will allow to change the Hat of the Flovatar any time.
502 // It checks for the right category and series before executing.
503 access(Flovatar.PrivateEnt)
504 fun setHat(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?{
505 if(component.getCategory() != "hat") {
506 panic("The component needs to be a hat")
507 }
508 if(component.getSeries() != self.metadata.series) {
509 panic("The hat belongs to a different series")
510 }
511 emit Updated(id: self.id)
512 let compNFT <- self.hat <- component
513 return <-compNFT
514 }
515
516 // This will allow to remove the Hat of the Flovatar any time.
517 access(Flovatar.PrivateEnt)
518 fun removeHat(): @FlovatarComponent.NFT?{
519 emit Updated(id: self.id)
520 let compNFT <- self.hat <- nil
521 return <-compNFT
522 }
523
524 access(all)
525 fun getEyeglasses(): UInt64?{
526 return self.eyeglasses?.templateId
527 }
528
529 // This will allow to change the Eyeglasses of the Flovatar any time.
530 // It checks for the right category and series before executing.
531 access(Flovatar.PrivateEnt)
532 fun setEyeglasses(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?{
533 if(component.getCategory() != "eyeglasses") {
534 panic("The component needs to be a pair of eyeglasses")
535 }
536 if(component.getSeries() != self.metadata.series) {
537 panic("The eyeglasses belongs to a different series")
538 }
539 emit Updated(id: self.id)
540 let compNFT <- self.eyeglasses <- component
541 return <-compNFT
542 }
543
544 // This will allow to remove the Eyeglasses of the Flovatar any time.
545 access(Flovatar.PrivateEnt)
546 fun removeEyeglasses(): @FlovatarComponent.NFT?{
547 emit Updated(id: self.id)
548 let compNFT <- self.eyeglasses <- nil
549 return <-compNFT
550 }
551
552 access(all)
553 fun getBackground(): UInt64?{
554 return self.background?.templateId
555 }
556
557 // This will allow to change the Background of the Flovatar any time.
558 // It checks for the right category and series before executing.
559 access(Flovatar.PrivateEnt)
560 fun setBackground(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?{
561 if(component.getCategory() != "background") {
562 panic("The component needs to be a background")
563 }
564 if(component.getSeries() != self.metadata.series) {
565 panic("The background belongs to a different series")
566 }
567 emit Updated(id: self.id)
568 let compNFT <- self.background <- component
569 return <-compNFT
570 }
571
572 // This will allow to remove the Background of the Flovatar any time.
573 access(Flovatar.PrivateEnt)
574 fun removeBackground(): @FlovatarComponent.NFT?{
575 emit Updated(id: self.id)
576 let compNFT <- self.background <- nil
577 return <-compNFT
578 }
579
580 // This function will return the full SVG of the Flovatar. It will take the
581 // optional components (Accessory, Hat, Eyeglasses and Background) from their
582 // original Template resources, while all the other unmutable components are
583 // taken from the Metadata directly.
584 access(all)
585 fun getSvg(): String{
586 var svg: String = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3000 3000' width='100%' height='100%'>"
587 if let background = self.getBackground(){
588 if let template = FlovatarComponentTemplate.getComponentTemplate(id: background){
589 svg = svg.concat(template.svg!)
590 }
591 }
592 svg = svg.concat(self.metadata.svg)
593 if let eyeglasses = self.getEyeglasses(){
594 if let template = FlovatarComponentTemplate.getComponentTemplate(id: eyeglasses){
595 svg = svg.concat(template.svg!)
596 }
597 }
598 if let hat = self.getHat(){
599 if let template = FlovatarComponentTemplate.getComponentTemplate(id: hat){
600 svg = svg.concat(template.svg!)
601 }
602 }
603 if let accessory = self.getAccessory(){
604 if let template = FlovatarComponentTemplate.getComponentTemplate(id: accessory){
605 svg = svg.concat(template.svg!)
606 }
607 }
608 svg = svg.concat("</svg>")
609 return svg
610 }
611
612 access(all)
613 fun getRarityScore(): UFix64{
614 var rareCount: UInt8 = self.metadata.rareCount
615 var epicCount: UInt8 = self.metadata.epicCount
616 var legendaryCount: UInt8 = self.metadata.legendaryCount
617 var totalBoosters: UInt8 = legendaryCount + epicCount + rareCount
618 let totalCommon: UInt8 = totalBoosters > UInt8(6) ? 0 : UInt8(6) - totalBoosters
619 if totalBoosters > UInt8(6){
620 if rareCount > UInt8(0){
621 rareCount = rareCount - UInt8(1)
622 } else if epicCount > UInt8(0){
623 epicCount = epicCount - UInt8(1)
624 } else if legendaryCount > UInt8(0){
625 legendaryCount = legendaryCount - UInt8(1)
626 }
627 }
628 let score: UInt64 = UInt64(legendaryCount) * UInt64(125) + UInt64(epicCount) * UInt64(25) + UInt64(rareCount) * UInt64(5) + UInt64(totalCommon)
629 let min: UInt64 = 6
630 let max: UInt64 = 6 * 125
631 let scoreFix: UFix64 = UFix64(score - min) * UFix64(100.0) / UFix64(max - min)
632 return scoreFix
633 }
634
635 access(all)
636 view fun getViews(): [Type]{
637 return [
638 Type<MetadataViews.NFTCollectionData>(),
639 Type<MetadataViews.NFTCollectionDisplay>(),
640 Type<MetadataViews.Display>(),
641 Type<MetadataViews.Royalties>(),
642 Type<MetadataViews.Edition>(),
643 Type<MetadataViews.ExternalURL>(),
644 Type<MetadataViews.Serial>(),
645 Type<MetadataViews.Traits>(),
646 Type<MetadataViews.EVMBridgedMetadata>()
647 ]
648 }
649
650 access(all)
651 fun resolveView(_ view: Type): AnyStruct?{
652 if view == Type<MetadataViews.ExternalURL>(){
653 return MetadataViews.ExternalURL("https://flovatar.com/flovatars/".concat(self.id.toString()))
654 }
655 if view == Type<MetadataViews.Royalties>(){
656 let royalties: [MetadataViews.Royalty] = []
657 var count: Int = 0
658 for royalty in self.royalties.royalty{
659 royalties.append(MetadataViews.Royalty(receiver: royalty.wallet, cut: royalty.cut, description: "Flovatar Royalty ".concat(count.toString())))
660 count = count + Int(1)
661 }
662 return MetadataViews.Royalties(royalties)
663 }
664 if view == Type<MetadataViews.Serial>(){
665 return MetadataViews.Serial(self.id)
666 }
667 if view == Type<MetadataViews.Editions>(){
668 let editionInfo = MetadataViews.Edition(name: "Flovatar Series 1", number: self.id, max: UInt64(9999))
669 let editionList: [MetadataViews.Edition] = [editionInfo]
670 return MetadataViews.Editions(editionList)
671 }
672 if view == Type<MetadataViews.NFTCollectionDisplay>(){
673 let mediaSquare = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://images.flovatar.com/logo.svg"), mediaType: "image/svg+xml")
674 let mediaBanner = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://images.flovatar.com/logo-horizontal.svg"), mediaType: "image/svg+xml")
675 return MetadataViews.NFTCollectionDisplay(name: "Flovatar", description: "Flovatar is pioneering a new way to unleash community creativity in Web3 by allowing users to be co-creators of their prized NFTs, instead of just being passive collectors.", externalURL: MetadataViews.ExternalURL("https://flovatar.com"), squareImage: mediaSquare, bannerImage: mediaBanner, socials:{ "discord": MetadataViews.ExternalURL("https://discord.gg/flovatar"), "twitter": MetadataViews.ExternalURL("https://twitter.com/flovatar"), "instagram": MetadataViews.ExternalURL("https://instagram.com/flovatar_nft"), "tiktok": MetadataViews.ExternalURL("https://www.tiktok.com/@flovatar")})
676 }
677 if view == Type<MetadataViews.Display>(){
678 return MetadataViews.Display(name: self.name == "" ? "Flovatar #".concat(self.id.toString()) : self.name, description: self.description, thumbnail: MetadataViews.HTTPFile(url: "https://images.flovatar.com/flovatar/svg/".concat(self.id.toString()).concat(".svg")))
679 }
680 if view == Type<MetadataViews.Traits>(){
681 let traits: [MetadataViews.Trait] = []
682 let components:{ String: UInt64} = self.metadata.getComponents()
683 for k in components.keys{
684 if let template = FlovatarComponentTemplate.getComponentTemplate(id: components[k]!){
685 let trait = MetadataViews.Trait(name: k, value: template.name, displayType: "String", rarity: MetadataViews.Rarity(score: nil, max: nil, description: template.rarity))
686 traits.append(trait)
687 }
688 }
689 if let accessory = self.getAccessory(){
690 if let template = FlovatarComponentTemplate.getComponentTemplate(id: accessory){
691 let trait = MetadataViews.Trait(name: template.category, value: template.name, displayType: "String", rarity: MetadataViews.Rarity(score: nil, max: nil, description: template.rarity))
692 traits.append(trait)
693 }
694 }
695 if let background = self.getBackground(){
696 if let template = FlovatarComponentTemplate.getComponentTemplate(id: background){
697 let trait = MetadataViews.Trait(name: template.category, value: template.name, displayType: "String", rarity: MetadataViews.Rarity(score: nil, max: nil, description: template.rarity))
698 traits.append(trait)
699 }
700 }
701 if let eyeglasses = self.getEyeglasses(){
702 if let template = FlovatarComponentTemplate.getComponentTemplate(id: eyeglasses){
703 let trait = MetadataViews.Trait(name: template.category, value: template.name, displayType: "String", rarity: MetadataViews.Rarity(score: nil, max: nil, description: template.rarity))
704 traits.append(trait)
705 }
706 }
707 if let hat = self.getHat(){
708 if let template = FlovatarComponentTemplate.getComponentTemplate(id: hat){
709 let trait = MetadataViews.Trait(name: template.category, value: template.name, displayType: "String", rarity: MetadataViews.Rarity(score: nil, max: nil, description: template.rarity))
710 traits.append(trait)
711 }
712 }
713 return MetadataViews.Traits(traits)
714 }
715 if view == Type<MetadataViews.Rarity>(){
716 return MetadataViews.Rarity(score: self.getRarityScore(), max: 100.0, description: nil)
717 }
718 if view == Type<MetadataViews.NFTCollectionData>(){
719 return MetadataViews.NFTCollectionData(storagePath: Flovatar.CollectionStoragePath, publicPath: Flovatar.CollectionPublicPath, publicCollection: Type<&Flovatar.Collection>(), publicLinkedType: Type<&Flovatar.Collection>(), createEmptyCollectionFunction: fun (): @{NonFungibleToken.Collection}{
720 return <-Flovatar.createEmptyCollection(nftType: Type<@Flovatar.Collection>())
721 })
722 }
723 if view == Type<MetadataViews.EVMBridgedMetadata>(){
724 let contractLevel = Flovatar.resolveContractView(
725 resourceType: nil,
726 viewType: Type<MetadataViews.EVMBridgedMetadata>()
727 ) as! MetadataViews.EVMBridgedMetadata?
728 ?? panic("Could not resolve contract-level EVMBridgedMetadata")
729
730 return MetadataViews.EVMBridgedMetadata(
731 name: contractLevel.name,
732 symbol: contractLevel.symbol,
733 uri: MetadataViews.URI(
734 baseURI: "https://flovatar.com/flovatars/json/",
735 value: self.id.toString()
736 )
737 )
738 }
739 return nil
740 }
741
742 access(all)
743 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
744 return <-create Collection()
745 }
746 }
747
748 // Standard NFT collectionPublic interface that can also borrowFlovatar as the correct type
749 access(all)
750 resource interface CollectionPublic{
751 access(all)
752 fun deposit(token: @{NonFungibleToken.NFT})
753
754 access(all)
755 fun getIDs(): [UInt64]
756
757 access(all)
758 view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
759
760 access(all)
761 fun borrowFlovatar(id: UInt64): &Flovatar.NFT?{
762 // If the result isn't nil, the id of the returned reference
763 // should be the same as the argument to the function
764 post{
765 result == nil || result?.id == id:
766 "Cannot borrow Flovatar reference: The ID of the returned reference is incorrect"
767 }
768 }
769 }
770
771 // Main Collection to manage all the Flovatar NFT
772 access(all)
773 resource Collection: CollectionPublic, NonFungibleToken.Collection {
774 // dictionary of NFT conforming tokens
775 // NFT is a resource type with an `UInt64` ID field
776 access(all)
777 var ownedNFTs: @{UInt64:{ NonFungibleToken.NFT}}
778
779 init(){
780 self.ownedNFTs <-{}
781 }
782
783 // withdraw removes an NFT from the collection and moves it to the caller
784 access(NonFungibleToken.Withdraw)
785 fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT}{
786 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
787 emit Withdraw(id: token.id, from: self.owner?.address)
788 return <-token
789 }
790
791 // deposit takes a NFT and adds it to the collections dictionary
792 // and adds the ID to the id array
793 access(all)
794 fun deposit(token: @{NonFungibleToken.NFT}){
795 let token <- token as! @Flovatar.NFT
796 let id: UInt64 = token.id
797
798 // add the new token to the dictionary which removes the old one
799 let oldToken <- self.ownedNFTs[id] <- token
800 emit Deposit(id: id, to: self.owner?.address)
801 destroy oldToken
802 }
803
804 // getIDs returns an array of the IDs that are in the collection
805 access(all)
806 view fun getIDs(): [UInt64]{
807 return self.ownedNFTs.keys
808 }
809
810 // borrowNFT gets a reference to an NFT in the collection
811 // so that the caller can read its metadata and call its methods
812 access(all)
813 view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?{
814 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
815 }
816
817 // borrowFlovatar returns a borrowed reference to a Flovatar
818 // so that the caller can read data and call methods from it.
819 access(all)
820 fun borrowFlovatar(id: UInt64): &Flovatar.NFT?{
821 if self.ownedNFTs[id] != nil {
822 let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
823 let flovatarNFT = ref as! &Flovatar.NFT
824 return flovatarNFT
825 } else {
826 return nil
827 }
828 }
829
830 /*
831 // borrowFlovatarPrivate returns a borrowed reference to a Flovatar using the Private interface
832 // so that the caller can read data and call methods from it, like setting the optional components.
833 */
834 access(Flovatar.PrivateEnt)
835 fun borrowFlovatarPrivate(id: UInt64): auth(Flovatar.PrivateEnt) &Flovatar.NFT?{
836 if self.ownedNFTs[id] != nil{
837 let ref = (&self.ownedNFTs[id] as auth(Flovatar.PrivateEnt) &{NonFungibleToken.NFT}?)!
838 return ref as! auth(Flovatar.PrivateEnt) &Flovatar.NFT
839 } else{
840 return nil
841 }
842 }
843
844
845 access(all)
846 view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?{
847 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
848 return nft as &{ViewResolver.Resolver}
849 }
850 return nil
851 }
852
853
854 /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
855 access(all)
856 view fun getSupportedNFTTypes(): {Type: Bool} {
857 let supportedTypes: {Type: Bool} = {}
858 supportedTypes[Type<@Flovatar.NFT>()] = true
859 return supportedTypes
860 }
861
862 /// Returns whether or not the given type is accepted by the collection
863 /// A collection that can accept any type should just return true by default
864 access(all)
865 view fun isSupportedNFTType(type: Type): Bool {
866 return type == Type<@Flovatar.NFT>()
867 }
868
869 access(all)
870 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
871 return <-Flovatar.createEmptyCollection(nftType: Type<@Flovatar.NFT>())
872 }
873
874
875 access(all) view fun getLength(): Int {
876 return self.ownedNFTs.length
877 }
878 }
879
880 // public function that anyone can call to create a new empty collection
881 access(all)
882 fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection}{
883 return <-create Collection()
884 }
885
886
887
888
889 access(all)
890 view fun getContractViews(resourceType: Type?): [Type] {
891 return [
892 Type<MetadataViews.NFTCollectionData>(),
893 Type<MetadataViews.NFTCollectionDisplay>(),
894 Type<MetadataViews.EVMBridgedMetadata>()
895 ]
896 }
897
898
899 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
900 switch viewType {
901 case Type<MetadataViews.NFTCollectionData>():
902 let collectionData = MetadataViews.NFTCollectionData(
903 storagePath: self.CollectionStoragePath,
904 publicPath: self.CollectionPublicPath,
905 publicCollection: Type<&Flovatar.Collection>(),
906 publicLinkedType: Type<&Flovatar.Collection>(),
907 createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
908 return <-Flovatar.createEmptyCollection(nftType: Type<@Flovatar.NFT>())
909 })
910 )
911 return collectionData
912 case Type<MetadataViews.NFTCollectionDisplay>():
913 let media = MetadataViews.Media(
914 file: MetadataViews.HTTPFile(
915 url: "https://images.flovatar.com/logo.svg"
916 ),
917 mediaType: "image/svg+xml"
918 )
919 let mediaBanner = MetadataViews.Media(
920 file: MetadataViews.HTTPFile(
921 url: "https://images.flovatar.com/logo-horizontal.svg"
922 ),
923 mediaType: "image/svg+xml"
924 )
925 return MetadataViews.NFTCollectionDisplay(
926 name: "Flovatar Collection",
927 description: "Flovatar is pioneering a new way to unleash community creativity in Web3 by allowing users to be co-creators of their prized NFTs, instead of just being passive collectors.",
928 externalURL: MetadataViews.ExternalURL("https://flovatar.com"),
929 squareImage: media,
930 bannerImage: mediaBanner,
931 socials: {
932 "twitter": MetadataViews.ExternalURL("https://x.com/flovatar"),
933 "discord": MetadataViews.ExternalURL("https://discord.gg/flovatar"),
934 "instagram": MetadataViews.ExternalURL("https://instagram.com/flovatar_nft"),
935 "tiktok": MetadataViews.ExternalURL("https://www.tiktok.com/@flovatar")
936 }
937 )
938 case Type<MetadataViews.EVMBridgedMetadata>():
939 // Implementing this view gives the project control over how the bridged NFT is represented as an ERC721
940 // when bridged to EVM on Flow via the public infrastructure bridge.
941
942 // Compose the contract-level URI. In this case, the contract metadata is located on some HTTP host,
943 // but it could be IPFS, S3, a data URL containing the JSON directly, etc.
944 return MetadataViews.EVMBridgedMetadata(
945 name: "Flovatar",
946 symbol: "FLVT",
947 uri: MetadataViews.URI(
948 baseURI: nil, // setting baseURI as nil sets the given value as the uri field value
949 value: "https://flovatar.com"
950 )
951 )
952 }
953 return nil
954 }
955
956
957 // This struct is used to send a data representation of the Flovatars
958 // when retrieved using the contract helper methods outside the collection.
959 access(all)
960 struct FlovatarData{
961 access(all)
962 let id: UInt64
963
964 access(all)
965 let name: String
966
967 access(all)
968 let metadata: Flovatar.Metadata
969
970 access(all)
971 let accessoryId: UInt64?
972
973 access(all)
974 let hatId: UInt64?
975
976 access(all)
977 let eyeglassesId: UInt64?
978
979 access(all)
980 let backgroundId: UInt64?
981
982 access(all)
983 let bio:{ String: String}
984
985 init(id: UInt64, name: String, metadata: Flovatar.Metadata, accessoryId: UInt64?, hatId: UInt64?, eyeglassesId: UInt64?, backgroundId: UInt64?, bio:{ String: String}){
986 self.id = id
987 self.name = name
988 self.metadata = metadata
989 self.accessoryId = accessoryId
990 self.hatId = hatId
991 self.eyeglassesId = eyeglassesId
992 self.backgroundId = backgroundId
993 self.bio = bio
994 }
995 }
996
997 // This function will look for a specific Flovatar on a user account and return a FlovatarData if found
998 access(all)
999 fun getFlovatar(address: Address, flovatarId: UInt64): FlovatarData?{
1000 let account = getAccount(address)
1001 if let flovatarCollection = account.capabilities.borrow<&Flovatar.Collection>(Flovatar.CollectionPublicPath){
1002 if !flovatarCollection.isInstance(Type<@Flovatar.Collection>()){
1003 panic("The Collection is not from the correct Type")
1004 }
1005 if let flovatar = flovatarCollection.borrowFlovatar(id: flovatarId){
1006 return FlovatarData(id: flovatarId, name: (flovatar!).getName(), metadata: (flovatar!).getMetadata(), accessoryId: (flovatar!).getAccessory(), hatId: (flovatar!).getHat(), eyeglassesId: (flovatar!).getEyeglasses(), backgroundId: (flovatar!).getBackground(), bio: (flovatar!).getBio())
1007 }
1008 }
1009 return nil
1010 }
1011
1012 // This function will look for a specific Flovatar on a user account and return the Score
1013 access(all)
1014 fun getFlovatarRarityScore(address: Address, flovatarId: UInt64): UFix64?{
1015 let account = getAccount(address)
1016 if let flovatarCollection = account.capabilities.borrow<&Flovatar.Collection>(Flovatar.CollectionPublicPath){
1017 if !flovatarCollection.isInstance(Type<@Flovatar.Collection>()){
1018 panic("The Collection is not from the correct Type")
1019 }
1020 if let flovatar = flovatarCollection.borrowFlovatar(id: flovatarId){
1021 return flovatar.getRarityScore()
1022 }
1023 }
1024
1025 return nil
1026 }
1027
1028 // This function will return all Flovatars on a user account and return an array of FlovatarData
1029 access(all)
1030 fun getFlovatars(address: Address): [FlovatarData]{
1031 var flovatarData: [FlovatarData] = []
1032 let account = getAccount(address)
1033 if let flovatarCollection = account.capabilities.borrow<&Flovatar.Collection>(Flovatar.CollectionPublicPath){
1034 if !flovatarCollection.isInstance(Type<@Flovatar.Collection>()){
1035 panic("The Collection is not from the correct Type")
1036 }
1037 for id in flovatarCollection.getIDs(){
1038 var flovatar = flovatarCollection.borrowFlovatar(id: id)
1039 let flovatarMetadata = (flovatar!).getMetadata()
1040 let newMetadata = Metadata(mint: flovatarMetadata.mint, series: flovatarMetadata.series, svg: "", combination: flovatarMetadata.combination, creatorAddress: flovatarMetadata.creatorAddress, components: flovatarMetadata.getComponents(), rareCount: flovatarMetadata.rareCount, epicCount: flovatarMetadata.epicCount, legendaryCount: flovatarMetadata.legendaryCount)
1041 flovatarData.append(FlovatarData(id: id, name: (flovatar!).getName(), metadata: newMetadata, accessoryId: (flovatar!).getAccessory(), hatId: (flovatar!).getHat(), eyeglassesId: (flovatar!).getEyeglasses(), backgroundId: (flovatar!).getBackground(), bio: (flovatar!).getBio()))
1042 }
1043 }
1044
1045 return flovatarData
1046 }
1047
1048 // This returns all the previously minted combinations, so that duplicates won't be allowed
1049 access(all)
1050 fun getMintedCombinations(): [String]{
1051 return Flovatar.mintedCombinations.keys
1052 }
1053
1054 // This returns all the previously minted names, so that duplicates won't be allowed
1055 access(all)
1056 fun getMintedNames(): [String]{
1057 return Flovatar.mintedNames.keys
1058 }
1059
1060 // This function will add a minted combination to the array
1061 access(account)
1062 fun addMintedCombination(combination: String){
1063 Flovatar.mintedCombinations.insert(key: combination, true)
1064 }
1065
1066 // This function will add a new name to the array
1067 access(account)
1068 fun addMintedName(name: String){
1069 Flovatar.mintedNames.insert(key: name, true)
1070 }
1071
1072 // This helper function will generate a string from a list of components,
1073 // to be used as a sort of barcode to keep the inventory of the minted
1074 // Flovatars and to avoid duplicates
1075 access(all)
1076 fun getCombinationString(body: UInt64, hair: UInt64, facialHair: UInt64?, eyes: UInt64, nose: UInt64, mouth: UInt64, clothing: UInt64): String{
1077 let facialHairString = facialHair != nil ? (facialHair!).toString() : "x"
1078 return "B".concat(body.toString()).concat("H").concat(hair.toString()).concat("F").concat(facialHairString).concat("E").concat(eyes.toString()).concat("N").concat(nose.toString()).concat("M").concat(mouth.toString()).concat("C").concat(clothing.toString())
1079 }
1080
1081 // This function will get a list of component IDs and will check if the
1082 // generated string is unique or if someone already used it before.
1083 access(all)
1084 fun checkCombinationAvailable(body: UInt64, hair: UInt64, facialHair: UInt64?, eyes: UInt64, nose: UInt64, mouth: UInt64, clothing: UInt64): Bool{
1085 let combinationString = Flovatar.getCombinationString(body: body, hair: hair, facialHair: facialHair, eyes: eyes, nose: nose, mouth: mouth, clothing: clothing)
1086 return !Flovatar.mintedCombinations.containsKey(combinationString)
1087 }
1088
1089 // This will check if a specific Name has already been taken
1090 // and assigned to some Flovatar
1091 access(all)
1092 fun checkNameAvailable(name: String): Bool{
1093 return name.length > 2 && name.length < 32 && !Flovatar.mintedNames.containsKey(name)
1094 }
1095
1096 // This is a public function that anyone can call to generate a new Flovatar
1097 // A list of components resources needs to be passed to executed.
1098 // It will check first for uniqueness of the combination + name and will then
1099 // generate the Flovatar and burn all the passed components.
1100 // The Spark NFT will entitle to use any common basic component (body, hair, etc.)
1101 // In order to use special rare components a boost of the same rarity will be needed
1102 // for each component used
1103 access(all)
1104 fun createFlovatar(spark: @FlovatarComponent.NFT, body: UInt64, hair: UInt64, facialHair: UInt64?, eyes: UInt64, nose: UInt64, mouth: UInt64, clothing: UInt64, accessory: @FlovatarComponent.NFT?, hat: @FlovatarComponent.NFT?, eyeglasses: @FlovatarComponent.NFT?, background: @FlovatarComponent.NFT?, rareBoost: @[FlovatarComponent.NFT], epicBoost: @[FlovatarComponent.NFT], legendaryBoost: @[FlovatarComponent.NFT], address: Address): @Flovatar.NFT{
1105 if(spark.getCategory() != "spark"){
1106 panic("The spark component belongs to the wrong category")
1107 }
1108 let bodyTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: body)!
1109 let hairTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: hair)!
1110 let eyesTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: eyes)!
1111 let noseTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: nose)!
1112 let mouthTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: mouth)!
1113 let clothingTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: clothing)!
1114
1115 // Make sure that all components belong to the correct category
1116 if bodyTemplate.category != "body"{
1117 panic("The body component belongs to the wrong category")
1118 }
1119 if hairTemplate.category != "hair"{
1120 panic("The hair component belongs to the wrong category")
1121 }
1122 if eyesTemplate.category != "eyes"{
1123 panic("The eyes component belongs to the wrong category")
1124 }
1125 if noseTemplate.category != "nose"{
1126 panic("The nose component belongs to the wrong category")
1127 }
1128 if mouthTemplate.category != "mouth"{
1129 panic("The mouth component belongs to the wrong category")
1130 }
1131 if clothingTemplate.category != "clothing"{
1132 panic("The clothing component belongs to the wrong category")
1133 }
1134 let sparkSeries = spark.getSeries()
1135 // Make sure that all the components belong to the same series like the spark
1136 if bodyTemplate.series != sparkSeries{
1137 panic("The body doesn't belong to the correct series")
1138 }
1139 if hairTemplate.series != sparkSeries{
1140 panic("The hair doesn't belong to the correct series")
1141 }
1142 if eyesTemplate.series != sparkSeries{
1143 panic("The eyes doesn't belong to the correct series")
1144 }
1145 if noseTemplate.series != sparkSeries{
1146 panic("The nose doesn't belong to the correct series")
1147 }
1148 if mouthTemplate.series != sparkSeries{
1149 panic("The mouth doesn't belong to the correct series")
1150 }
1151 if clothingTemplate.series != sparkSeries{
1152 panic("The clothing doesn't belong to the correct series")
1153 }
1154
1155 // Make more checks for the additional components to check for the right category and uniqueness
1156 var facialHairTemplate: FlovatarComponentTemplate.ComponentTemplateData? = nil
1157 if facialHair != nil{
1158 facialHairTemplate = FlovatarComponentTemplate.getComponentTemplate(id: facialHair!)
1159 if facialHairTemplate?.category != "facialHair"{
1160 panic("The facial hair component belongs to the wrong category")
1161 }
1162 if facialHairTemplate?.series != sparkSeries{
1163 panic("The facial hair doesn't belong to the correct series")
1164 }
1165 }
1166 if accessory != nil{
1167 if !accessory?.checkCategorySeries(category: "accessory", series: sparkSeries)!{
1168 panic("The accessory component belongs to the wrong category or the wrong series")
1169 }
1170 }
1171 if hat != nil{
1172 if !hat?.checkCategorySeries(category: "hat", series: sparkSeries)!{
1173 panic("The hat component belongs to the wrong category or the wrong series")
1174 }
1175 }
1176 if eyeglasses != nil{
1177 if !eyeglasses?.checkCategorySeries(category: "eyeglasses", series: sparkSeries)!{
1178 panic("The eyeglasses component belongs to the wrong category or the wrong series")
1179 }
1180 }
1181 if background != nil{
1182 if !background?.checkCategorySeries(category: "background", series: sparkSeries)!{
1183 panic("The background component belongs to the wrong category or the wrong series")
1184 }
1185 }
1186
1187 //Make sure that all the Rarity Boosts are from the correct category
1188 var i: Int = 0
1189 while i < rareBoost.length{
1190 if !rareBoost[i].isBooster(rarity: "rare"){
1191 panic("The rare boost belongs to the wrong category")
1192 }
1193 if rareBoost[i].getSeries() != sparkSeries{
1194 panic("The rare boost doesn't belong to the correct series")
1195 }
1196 i = i + 1
1197 }
1198 i = 0
1199 while i < epicBoost.length{
1200 if !epicBoost[i].isBooster(rarity: "epic"){
1201 panic("The epic boost belongs to the wrong category")
1202 }
1203 if epicBoost[i].getSeries() != sparkSeries{
1204 panic("The epic boost doesn't belong to the correct series")
1205 }
1206 i = i + 1
1207 }
1208 i = 0
1209 while i < legendaryBoost.length{
1210 if !legendaryBoost[i].isBooster(rarity: "legendary"){
1211 panic("The legendary boost belongs to the wrong category")
1212 }
1213 if legendaryBoost[i].getSeries() != sparkSeries{
1214 panic("The legendary boost doesn't belong to the correct series")
1215 }
1216 i = i + 1
1217 }
1218
1219 //Keep count of the necessary rarity boost for the selected templates
1220 var rareCount: UInt8 = 0
1221 var epicCount: UInt8 = 0
1222 var legendaryCount: UInt8 = 0
1223 if bodyTemplate.rarity == "rare"{
1224 rareCount = rareCount + 1
1225 }
1226 if hairTemplate.rarity == "rare"{
1227 rareCount = rareCount + 1
1228 }
1229 if eyesTemplate.rarity == "rare"{
1230 rareCount = rareCount + 1
1231 }
1232 if noseTemplate.rarity == "rare"{
1233 rareCount = rareCount + 1
1234 }
1235 if mouthTemplate.rarity == "rare"{
1236 rareCount = rareCount + 1
1237 }
1238 if clothingTemplate.rarity == "rare"{
1239 rareCount = rareCount + 1
1240 }
1241 if bodyTemplate.rarity == "epic"{
1242 epicCount = epicCount + 1
1243 }
1244 if hairTemplate.rarity == "epic"{
1245 epicCount = epicCount + 1
1246 }
1247 if eyesTemplate.rarity == "epic"{
1248 epicCount = epicCount + 1
1249 }
1250 if noseTemplate.rarity == "epic"{
1251 epicCount = epicCount + 1
1252 }
1253 if mouthTemplate.rarity == "epic"{
1254 epicCount = epicCount + 1
1255 }
1256 if clothingTemplate.rarity == "epic"{
1257 epicCount = epicCount + 1
1258 }
1259 if bodyTemplate.rarity == "legendary"{
1260 legendaryCount = legendaryCount + 1
1261 }
1262 if hairTemplate.rarity == "legendary"{
1263 legendaryCount = legendaryCount + 1
1264 }
1265 if eyesTemplate.rarity == "legendary"{
1266 legendaryCount = legendaryCount + 1
1267 }
1268 if noseTemplate.rarity == "legendary"{
1269 legendaryCount = legendaryCount + 1
1270 }
1271 if mouthTemplate.rarity == "legendary"{
1272 legendaryCount = legendaryCount + 1
1273 }
1274 if clothingTemplate.rarity == "legendary"{
1275 legendaryCount = legendaryCount + 1
1276 }
1277 if facialHairTemplate != nil{
1278 if facialHairTemplate?.rarity == "rare"{
1279 rareCount = rareCount + 1
1280 }
1281 if facialHairTemplate?.rarity == "epic"{
1282 epicCount = epicCount + 1
1283 }
1284 if facialHairTemplate?.rarity == "legendary"{
1285 legendaryCount = legendaryCount + 1
1286 }
1287 }
1288 if Int(rareCount) != rareBoost.length{
1289 panic("The rare boosts are not equal the ones needed")
1290 }
1291 if Int(epicCount) != epicBoost.length{
1292 panic("The epic boosts are not equal the ones needed")
1293 }
1294 if Int(legendaryCount) != legendaryBoost.length{
1295 panic("The epic boosts are not equal the ones needed")
1296 }
1297
1298 // Generates the combination string to check for uniqueness.
1299 // This is like a barcode that defines exactly which components were used
1300 // to create the Flovatar
1301 let combinationString = Flovatar.getCombinationString(body: body, hair: hair, facialHair: facialHair, eyes: eyes, nose: nose, mouth: mouth, clothing: clothing)
1302
1303 // Makes sure that the combination is available and not taken already
1304 if Flovatar.mintedCombinations.containsKey(combinationString) == true{
1305 panic("This combination has already been taken")
1306 }
1307 let facialHairSvg: String = facialHairTemplate != nil ? facialHairTemplate?.svg! : ""
1308 let svg = (bodyTemplate.svg!).concat(clothingTemplate.svg!).concat(hairTemplate.svg!).concat(eyesTemplate.svg!).concat(noseTemplate.svg!).concat(mouthTemplate.svg!).concat(facialHairSvg)
1309
1310 // TODO fix this with optional if possible. If I define it as UInt64?
1311 // instead of UInt64 it's throwing an error even if it's defined in Metadata struct
1312 let facialHairId: UInt64 = facialHair != nil ? facialHair! : 0
1313
1314 // Creates the metadata for the new Flovatar
1315 let metadata = Metadata(mint: Flovatar.totalSupply + UInt64(1), series: spark.getSeries(), svg: svg, combination: combinationString, creatorAddress: address, components:{ "body": body, "hair": hair, "facialHair": facialHairId, "eyes": eyes, "nose": nose, "mouth": mouth, "clothing": clothing}, rareCount: rareCount, epicCount: epicCount, legendaryCount: legendaryCount)
1316 let royalties: [Royalty] = []
1317 let creatorAccount = getAccount(address)
1318 royalties.append(Royalty(wallet: creatorAccount.capabilities.get<&FlowToken.Vault>(/public/flowTokenReceiver), cut: Flovatar.getRoyaltyCut(), type: RoyaltyType.percentage))
1319 royalties.append(Royalty(wallet: self.account.capabilities.get<&FlowToken.Vault>(/public/flowTokenReceiver), cut: Flovatar.getMarketplaceCut(), type: RoyaltyType.percentage))
1320
1321 // Mint the new Flovatar NFT by passing the metadata to it
1322 var newNFT <- create NFT(metadata: metadata, royalties: Royalties(royalty: royalties))
1323
1324 // Adds the combination to the arrays to remember it
1325 Flovatar.addMintedCombination(combination: combinationString)
1326
1327 // Checks for any additional optional component (accessory, hat,
1328 // eyeglasses, background) and assigns it to the Flovatar if present.
1329 if accessory != nil{
1330 let temp <- newNFT.setAccessory(component: <-accessory!)
1331 destroy temp
1332 } else{
1333 destroy accessory
1334 }
1335 if hat != nil{
1336 let temp <- newNFT.setHat(component: <-hat!)
1337 destroy temp
1338 } else{
1339 destroy hat
1340 }
1341 if eyeglasses != nil{
1342 let temp <- newNFT.setEyeglasses(component: <-eyeglasses!)
1343 destroy temp
1344 } else{
1345 destroy eyeglasses
1346 }
1347 if background != nil{
1348 let temp <- newNFT.setBackground(component: <-background!)
1349 destroy temp
1350 } else{
1351 destroy background
1352 }
1353
1354 // Emits the Created event to notify about its existence
1355 emit Created(id: newNFT.id, metadata: metadata)
1356
1357 // Destroy all the spark and the rarity boost since they are not needed anymore.
1358 destroy spark
1359 while rareBoost.length > 0{
1360 let boost <- rareBoost.remove(at: 0)
1361 destroy boost
1362 }
1363 destroy rareBoost
1364 while epicBoost.length > 0{
1365 let boost <- epicBoost.remove(at: 0)
1366 destroy boost
1367 }
1368 destroy epicBoost
1369 while legendaryBoost.length > 0{
1370 let boost <- legendaryBoost.remove(at: 0)
1371 destroy boost
1372 }
1373 destroy legendaryBoost
1374 return <-newNFT
1375 }
1376
1377 // These functions will return the current Royalty cuts for
1378 // both the Creator and the Marketplace.
1379 access(all)
1380 fun getRoyaltyCut(): UFix64{
1381 return self.royaltyCut
1382 }
1383
1384 access(all)
1385 fun getMarketplaceCut(): UFix64{
1386 return self.marketplaceCut
1387 }
1388
1389 // Only Admins will be able to call the set functions to
1390 // manage Royalties and Marketplace cuts.
1391 access(account)
1392 fun setRoyaltyCut(value: UFix64){
1393 self.royaltyCut = value
1394 }
1395
1396 access(account)
1397 fun setMarketplaceCut(value: UFix64){
1398 self.marketplaceCut = value
1399 }
1400
1401 // This is the main Admin resource that will allow the owner
1402 // to generate new Templates, Components and Packs
1403 access(all)
1404 resource Admin{
1405
1406
1407
1408
1409 //This will create a new FlovatarComponentTemplate that
1410 // contains all the SVG and basic informations to represent
1411 // a specific part of the Flovatar (body, hair, eyes, mouth, etc.)
1412 // More info in the FlovatarComponentTemplate.cdc file
1413 access(all)
1414 fun createComponentTemplate(name: String, category: String, color: String, description: String, svg: String, series: UInt32, maxMintableComponents: UInt64, rarity: String): @FlovatarComponentTemplate.ComponentTemplate{
1415 return <-FlovatarComponentTemplate.createComponentTemplate(name: name, category: category, color: color, description: description, svg: svg, series: series, maxMintableComponents: maxMintableComponents, rarity: rarity)
1416 }
1417
1418 // This will mint a new Component based from a selected Template
1419 access(all)
1420 fun createComponent(templateId: UInt64): @FlovatarComponent.NFT{
1421 return <-FlovatarComponent.createComponent(templateId: templateId)
1422 }
1423
1424 // This will mint Components in batch and return a Collection instead of the single NFT
1425 access(all)
1426 fun batchCreateComponents(templateId: UInt64, quantity: UInt64): @FlovatarComponent.Collection{
1427 return <-FlovatarComponent.batchCreateComponents(templateId: templateId, quantity: quantity)
1428 }
1429
1430 // This function will generate a new Pack containing a set of components.
1431 // A random string is passed to manage permissions for the
1432 // purchase of it (more info on FlovatarPack.cdc).
1433 // Finally the sale price is set as well.
1434 access(all)
1435 fun createPack(components: @[FlovatarComponent.NFT], randomString: String, price: UFix64, sparkCount: UInt32, series: UInt32, name: String): @FlovatarPack.Pack{
1436 return <-FlovatarPack.createPack(components: <-components, randomString: randomString, price: price, sparkCount: sparkCount, series: series, name: name)
1437 }
1438
1439
1440 // With this function you can generate a new Admin resource
1441 // and pass it to another user if needed
1442 access(all)
1443 fun createNewAdmin(): @Admin{
1444 return <-create Admin()
1445 }
1446
1447 // Helper functions to update the Royalty cut
1448 access(all)
1449 fun setRoyaltyCut(value: UFix64){
1450 Flovatar.setRoyaltyCut(value: value)
1451 }
1452
1453 // Helper functions to update the Marketplace cut
1454 access(all)
1455 fun setMarketplaceCut(value: UFix64){
1456 Flovatar.setMarketplaceCut(value: value)
1457 }
1458 }
1459
1460 init(){
1461 self.CollectionPublicPath = /public/FlovatarCollection
1462 self.CollectionStoragePath = /storage/FlovatarCollection
1463 self.AdminStoragePath = /storage/FlovatarAdmin
1464
1465 // Initialize the total supply
1466 self.totalSupply = UInt64(0)
1467 self.mintedCombinations ={}
1468 self.mintedNames ={}
1469
1470 // Set the default Royalty and Marketplace cuts
1471 self.royaltyCut = 0.01
1472 self.marketplaceCut = 0.05
1473 self.account.storage.save<@{NonFungibleToken.Collection}>(<-Flovatar.createEmptyCollection(nftType: Type<@Flovatar.Collection>()), to: Flovatar.CollectionStoragePath)
1474 var capability_1 = self.account.capabilities.storage.issue<&{Flovatar.CollectionPublic}>(Flovatar.CollectionStoragePath)
1475 self.account.capabilities.publish(capability_1, at: Flovatar.CollectionPublicPath)
1476
1477 // Put the Admin resource in storage
1478 self.account.storage.save<@Admin>(<-create Admin(), to: self.AdminStoragePath)
1479 emit ContractInitialized()
1480 }
1481}
1482