Smart Contract
Flobot
A.921ea449dffec68a.Flobot
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 ViewResolver from 0x1d7e57aa55817448
9
10/*
11
12The contract that defines the Flobot NFT and a Collection to manage them
13
14Base components that will be used to generate the unique combination of the Flobot
15'body', 'hair', 'facialhair', 'eyes', 'nose', 'mouth', 'clothing'
16
17Extra components that can be added in a second moment
18'accessory', 'hat', eyeglass', 'background'
19
20
21This contract contains also the Admin resource that can be used to manage and generate all the other ones (Components, Templates, Packs).
22
23 */
24
25access(all)
26contract Flobot: NonFungibleToken{
27 access(all)
28 let CollectionStoragePath: StoragePath
29
30 access(all)
31 let CollectionPublicPath: PublicPath
32
33 access(all)
34 let AdminStoragePath: StoragePath
35
36 // These will be used in the Marketplace to pay out
37 // royalties to the creator and to the marketplace
38 access(account)
39 var royaltyCut: UFix64
40
41 access(account)
42 var marketplaceCut: UFix64
43
44 // Here we keep track of all the Flobot unique combinations and names
45 // that people will generate to make sure that there are no duplicates
46 access(all)
47 var totalSupply: UInt64
48
49 access(contract)
50 let mintedCombinations:{ String: Bool}
51
52 access(contract)
53 let mintedNames:{ String: Bool}
54
55 access(all)
56 event ContractInitialized()
57
58 access(all)
59 event Withdraw(id: UInt64, from: Address?)
60
61 access(all)
62 event Deposit(id: UInt64, to: Address?)
63
64 access(all)
65 event Created(id: UInt64, metadata: Metadata)
66
67 access(all)
68 event Updated(id: UInt64)
69
70 access(all)
71 event NameSet(id: UInt64, name: String)
72
73 access(all)
74 struct Royalties{
75 access(all)
76 let royalty: [Royalty]
77
78 init(royalty: [Royalty]){
79 self.royalty = royalty
80 }
81 }
82
83 access(all)
84 enum RoyaltyType: UInt8{
85 access(all)
86 case fixed
87
88 access(all)
89 case percentage
90 }
91
92 access(all)
93 struct Royalty{
94 access(all)
95 let wallet: Capability<&{FungibleToken.Receiver}>
96
97 access(all)
98 let cut: UFix64
99
100 //can be percentage
101 access(all)
102 let type: RoyaltyType
103
104 init(wallet: Capability<&{FungibleToken.Receiver}>, cut: UFix64, type: RoyaltyType){
105 self.wallet = wallet
106 self.cut = cut
107 self.type = type
108 }
109 }
110
111 // This Metadata struct contains all the most important informations about the Flobot
112 access(all)
113 struct Metadata{
114 access(all)
115 let mint: UInt64
116
117 access(all)
118 let series: UInt32
119
120 access(all)
121 let combination: String
122
123 access(all)
124 let rarity: String
125
126 access(all)
127 let creatorAddress: Address
128
129 access(self)
130 let components:{ String: UInt64}
131
132 init(mint: UInt64, series: UInt32, combination: String, rarity: String, creatorAddress: Address, components:{ String: UInt64}){
133 self.mint = mint
134 self.series = series
135 self.combination = combination
136 self.rarity = rarity
137 self.creatorAddress = creatorAddress
138 self.components = components
139 }
140
141 access(all)
142 fun getComponents():{ String: UInt64}{
143 return self.components
144 }
145 }
146
147 access(all) entitlement PrivateEnt
148
149 // The public interface can show metadata and the content for the Flobot.
150 // In addition to it, it provides methods to access the additional optional
151 // components (accessory, hat, eyeglasses, background) for everyone.
152 access(all)
153 resource interface Public{
154 access(all)
155 let id: UInt64
156
157 access(contract)
158 let metadata: Metadata
159
160 access(contract)
161 let royalties: Royalties
162
163 // these three are added because I think they will be in the standard. At least Dieter thinks it will be needed
164 access(contract)
165 var name: String
166
167 access(all)
168 let description: String
169
170 access(all)
171 let schema: String?
172
173 access(all)
174 fun getName(): String
175
176 access(all)
177 fun getBackground(): UInt64?
178
179 access(all)
180 fun getSvg(): String
181
182 access(all)
183 fun getMetadata(): Metadata
184
185 access(all)
186 fun getRoyalties(): Royalties
187
188 access(all)
189 fun getBio():{ String: String}
190 }
191
192 //The private interface can update the Accessory, Hat, Eyeglasses and Background
193 //for the Flobot and is accessible only to the owner of the NFT
194 access(all)
195 resource interface Private{
196 access(Flobot.PrivateEnt)
197 fun setName(name: String): String
198
199 access(Flobot.PrivateEnt)
200 fun setBackground(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?
201
202 access(Flobot.PrivateEnt)
203 fun removeBackground(): @FlovatarComponent.NFT?
204 }
205
206 //The NFT resource that implements both Private and Public interfaces
207 access(all)
208 resource NFT: NonFungibleToken.NFT, Public, Private {
209 access(all)
210 let id: UInt64
211
212 access(contract)
213 let metadata: Metadata
214
215 access(contract)
216 let royalties: Royalties
217
218 access(contract)
219 var background: @FlovatarComponent.NFT?
220
221 access(contract)
222 var name: String
223
224 access(all)
225 let description: String
226
227 access(all)
228 let schema: String?
229
230 access(self)
231 let bio:{ String: String}
232
233 init(metadata: Metadata, royalties: Royalties){
234 Flobot.totalSupply = Flobot.totalSupply + 1
235 self.id = Flobot.totalSupply
236 self.metadata = metadata
237 self.royalties = royalties
238 self.background <- nil
239 self.schema = nil
240 self.name = ""
241 self.description = ""
242 self.bio ={}
243 }
244
245 access(all)
246 fun getID(): UInt64{
247 return self.id
248 }
249
250 access(all)
251 fun getMetadata(): Metadata{
252 return self.metadata
253 }
254
255 access(all)
256 fun getRoyalties(): Royalties{
257 return self.royalties
258 }
259
260 access(all)
261 fun getBio():{ String: String}{
262 return self.bio
263 }
264
265 access(all)
266 fun getName(): String{
267 return self.name
268 }
269
270 // This will allow to change the Name of the Flobot only once.
271 // It checks for the current name is empty, otherwise it will throw an error.
272 access(Flobot.PrivateEnt)
273 fun setName(name: String): String{
274 pre{
275 // TODO: Make sure that the text of the name is sanitized
276 //and that bad words are not accepted?
277 name.length > 2:
278 "The name is too short"
279 name.length < 32:
280 "The name is too long"
281 self.name == "":
282 "The name has already been set"
283 }
284
285 // Makes sure that the name is available and not taken already
286 if Flobot.checkNameAvailable(name: name) == false{
287 panic("This name has already been taken")
288 }
289
290 // DISABLING THIS FUNCTIONALITY TO BE INTRODUCED AT A LATER DATE
291 //self.name = name
292
293 // Adds the name to the array to remember it
294 //Flobot.addMintedName(name: name)
295 //emit NameSet(id: self.id, name: name)
296 return self.name
297 }
298
299 access(all)
300 fun getBackground(): UInt64?{
301 return self.background?.templateId
302 }
303
304 // This will allow to change the Background of the Flobot any time.
305 // It checks for the right category and series before executing.
306 access(Flobot.PrivateEnt)
307 fun setBackground(component: @FlovatarComponent.NFT): @FlovatarComponent.NFT?{
308 if(component.getCategory() != "background") {
309 panic("The component needs to be a background")
310 }
311 if(component.getSeries() != UInt32(1)) {
312 panic("The accessory doesn't belong to series 1")
313 }
314
315 emit Updated(id: self.id)
316 let compNFT <- self.background <- component
317 return <-compNFT
318 }
319
320 // This will allow to remove the Background of the Flobot any time.
321 access(Flobot.PrivateEnt)
322 fun removeBackground(): @FlovatarComponent.NFT?{
323 emit Updated(id: self.id)
324 let compNFT <- self.background <- nil
325 return <-compNFT
326 }
327
328 // This function will return the full SVG of the Flobot. It will take the
329 // optional components (Accessory, Hat, Eyeglasses and Background) from their
330 // original Template resources, while all the other unmutable components are
331 // taken from the Metadata directly.
332 access(all)
333 fun getSvg(): String{
334 var svg: String = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3000 3000' width='100%' height='100%'>"
335 if let background = self.getBackground(){
336 if let template = FlovatarComponentTemplate.getComponentTemplate(id: background){
337 svg = svg.concat(template.svg!)
338 }
339 }
340 svg = svg.concat(self.getTraitsSvg())
341 svg = svg.concat("</svg>")
342 return svg
343 }
344
345 access(all)
346 fun getSvgNoBg(): String{
347 var svg: String = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3000 3000' width='100%' height='100%'>"
348 svg = svg.concat(self.getTraitsSvg())
349 svg = svg.concat("</svg>")
350 return svg
351 }
352
353 access(all)
354 fun getTraitsSvg(): String{
355 var svg: String = ""
356 let components:{ String: UInt64} = self.metadata.getComponents()
357 let armsTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: components["arms"]!)!
358 let legsTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: components["legs"]!)!
359 let bodyTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: components["body"]!)!
360 let headTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: components["head"]!)!
361 let faceTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: components["face"]!)!
362 svg = svg.concat(armsTemplate.svg!)
363 svg = svg.concat(legsTemplate.svg!)
364 svg = svg.concat(bodyTemplate.svg!)
365 svg = svg.concat(headTemplate.svg!)
366 svg = svg.concat(faceTemplate.svg!)
367 return svg
368 }
369
370 access(all)
371 view fun getViews(): [Type]{
372 return [
373 Type<MetadataViews.NFTCollectionData>(),
374 Type<MetadataViews.NFTCollectionDisplay>(),
375 Type<MetadataViews.Display>(),
376 Type<MetadataViews.Royalties>(),
377 Type<MetadataViews.Edition>(),
378 Type<MetadataViews.ExternalURL>(),
379 Type<MetadataViews.Serial>(),
380 Type<MetadataViews.Traits>(),
381 Type<MetadataViews.EVMBridgedMetadata>()
382 ]
383 }
384
385 access(all)
386 fun resolveView(_ view: Type): AnyStruct?{
387 if view == Type<MetadataViews.ExternalURL>(){
388 return MetadataViews.ExternalURL("https://flovatar.com/flobots/".concat(self.id.toString()))
389 }
390 if view == Type<MetadataViews.Royalties>(){
391 let royalties: [MetadataViews.Royalty] = []
392 var count: Int = 0
393 for royalty in self.royalties.royalty{
394 royalties.append(MetadataViews.Royalty(receiver: royalty.wallet, cut: royalty.cut, description: "Flovatar Royalty ".concat(count.toString())))
395 count = count + 1
396 }
397 return MetadataViews.Royalties(royalties)
398 }
399 if view == Type<MetadataViews.Serial>(){
400 return MetadataViews.Serial(self.id)
401 }
402 if view == Type<MetadataViews.Editions>(){
403 let editionInfo = MetadataViews.Edition(name: "Flobots", number: self.id, max: 9999)
404 let editionList: [MetadataViews.Edition] = [editionInfo]
405 return MetadataViews.Editions(editionList)
406 }
407 if view == Type<MetadataViews.NFTCollectionDisplay>(){
408 let mediaSquare = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://images.flovatar.com/logo.svg"), mediaType: "image/svg+xml")
409 let mediaBanner = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://images.flovatar.com/logo-horizontal.svg"), mediaType: "image/svg+xml")
410 return MetadataViews.NFTCollectionDisplay(name: "Flovatar Flobot", 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")})
411 }
412 if view == Type<MetadataViews.NFTCollectionData>(){
413 return MetadataViews.NFTCollectionData(storagePath: Flobot.CollectionStoragePath, publicPath: Flobot.CollectionPublicPath, publicCollection: Type<&Flobot.Collection>(), publicLinkedType: Type<&Flobot.Collection>(), createEmptyCollectionFunction: fun (): @{NonFungibleToken.Collection}{
414 return <-Flobot.createEmptyCollection(nftType: Type<@Flobot.Collection>())
415 })
416 }
417 if view == Type<MetadataViews.Display>(){
418 return MetadataViews.Display(name: self.name == "" ? "Flobot #".concat(self.id.toString()) : self.name, description: self.description, thumbnail: MetadataViews.HTTPFile(url: "https://images.flovatar.com/flobot/svg/".concat(self.id.toString()).concat(".svg")))
419 }
420 if view == Type<MetadataViews.Traits>(){
421 let traits: [MetadataViews.Trait] = []
422 let components:{ String: UInt64} = self.metadata.getComponents()
423 for k in components.keys{
424 if let template = FlovatarComponentTemplate.getComponentTemplate(id: components[k]!){
425 let trait = MetadataViews.Trait(name: k, value: template.name, displayType: "String", rarity: MetadataViews.Rarity(score: nil, max: nil, description: template.rarity))
426 traits.append(trait)
427 }
428 }
429 if let background = self.getBackground(){
430 if let template = FlovatarComponentTemplate.getComponentTemplate(id: background){
431 let trait = MetadataViews.Trait(name: template.category, value: template.name, displayType: "String", rarity: MetadataViews.Rarity(score: nil, max: nil, description: template.rarity))
432 traits.append(trait)
433 }
434 }
435 return MetadataViews.Traits(traits)
436 }
437 if view == Type<MetadataViews.Rarity>(){
438 var score: UFix64 = 10.0
439 if self.metadata.rarity == "legendary"{
440 score = 100.0
441 } else if self.metadata.rarity == "epic"{
442 score = 50.0
443 }
444 return MetadataViews.Rarity(score: score, max: 100.0, description: nil)
445 }
446 if view == Type<MetadataViews.EVMBridgedMetadata>(){
447 let contractLevel = Flobot.resolveContractView(
448 resourceType: nil,
449 viewType: Type<MetadataViews.EVMBridgedMetadata>()
450 ) as! MetadataViews.EVMBridgedMetadata?
451 ?? panic("Could not resolve contract-level EVMBridgedMetadata")
452
453 return MetadataViews.EVMBridgedMetadata(
454 name: contractLevel.name,
455 symbol: contractLevel.symbol,
456 uri: MetadataViews.URI(
457 baseURI: "https://flovatar.com/flobots/json/",
458 value: self.id.toString()
459 )
460 )
461 }
462 return nil
463 }
464
465 access(all)
466 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
467 return <-Flobot.createEmptyCollection(nftType: Type<@Flobot.NFT>())
468 }
469 }
470
471 // Standard NFT collectionPublic interface that can also borrowFlobot as the correct type
472 access(all)
473 resource interface CollectionPublic{
474 access(all)
475 fun deposit(token: @{NonFungibleToken.NFT})
476
477 access(all)
478 fun getIDs(): [UInt64]
479
480 access(all)
481 view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
482
483 access(all)
484 fun borrowFlobot(id: UInt64): &Flobot.NFT?{
485 // If the result isn't nil, the id of the returned reference
486 // should be the same as the argument to the function
487 post{
488 result == nil || result?.id == id:
489 "Cannot borrow Flobot reference: The ID of the returned reference is incorrect"
490 }
491 }
492 }
493
494 // Main Collection to manage all the Flobot NFT
495 access(all)
496 resource Collection: CollectionPublic, NonFungibleToken.Collection {
497 // dictionary of NFT conforming tokens
498 // NFT is a resource type with an `UInt64` ID field
499 access(all)
500 var ownedNFTs: @{UInt64:{ NonFungibleToken.NFT}}
501
502 init(){
503 self.ownedNFTs <-{}
504 }
505
506 /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
507 access(all)
508 view fun getSupportedNFTTypes(): {Type: Bool} {
509 let supportedTypes: {Type: Bool} = {}
510 supportedTypes[Type<@Flobot.NFT>()] = true
511 return supportedTypes
512 }
513
514 /// Returns whether or not the given type is accepted by the collection
515 /// A collection that can accept any type should just return true by default
516 access(all)
517 view fun isSupportedNFTType(type: Type): Bool {
518 return type == Type<@Flobot.NFT>()
519 }
520
521 // withdraw removes an NFT from the collection and moves it to the caller
522 access(NonFungibleToken.Withdraw)
523 fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT}{
524 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
525 return <-token
526 }
527
528 // deposit takes a NFT and adds it to the collections dictionary
529 // and adds the ID to the id array
530 access(all)
531 fun deposit(token: @{NonFungibleToken.NFT}){
532 let token <- token as! @Flobot.NFT
533 let id: UInt64 = token.id
534
535 // add the new token to the dictionary which removes the old one
536 let oldToken <- self.ownedNFTs[id] <- token
537 destroy oldToken
538 }
539
540 // getIDs returns an array of the IDs that are in the collection
541 access(all)
542 view fun getIDs(): [UInt64]{
543 return self.ownedNFTs.keys
544 }
545
546 access(all) view fun getLength(): Int {
547 return self.ownedNFTs.length
548 }
549
550 // borrowNFT gets a reference to an NFT in the collection
551 // so that the caller can read its metadata and call its methods
552 access(all)
553 view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?{
554 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
555 }
556
557
558
559 // borrowFlobot returns a borrowed reference to a Flobot
560 // so that the caller can read data and call methods from it.
561 access(all)
562 fun borrowFlobot(id: UInt64): &Flobot.NFT?{
563 if self.ownedNFTs[id] != nil {
564 let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
565 let flobotNFT = ref as! &Flobot.NFT
566 return flobotNFT
567 } else {
568 return nil
569 }
570 }
571
572 /*
573 // borrowFlobotPrivate returns a borrowed reference to a Flobot using the Private interface
574 // so that the caller can read data and call methods from it, like setting the optional components.
575 */
576 access(Flobot.PrivateEnt)
577 fun borrowFlobotPrivate(id: UInt64): auth(Flobot.PrivateEnt) &Flobot.NFT?{
578 if self.ownedNFTs[id] != nil{
579 let ref = (&self.ownedNFTs[id] as auth(Flobot.PrivateEnt) &{NonFungibleToken.NFT}?)!
580 return ref as! auth(Flobot.PrivateEnt) &Flobot.NFT
581 } else{
582 return nil
583 }
584 }
585
586
587
588 access(all)
589 view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?{
590 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
591 return nft as &{ViewResolver.Resolver}
592 }
593 return nil
594 }
595
596 access(all)
597 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
598 return <-Flobot.createEmptyCollection(nftType: Type<@Flobot.NFT>())
599 }
600 }
601
602 // public function that anyone can call to create a new empty collection
603 access(all)
604 fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection}{
605 return <-create Collection()
606 }
607
608 access(all)
609 view fun getContractViews(resourceType: Type?): [Type] {
610 return [
611 Type<MetadataViews.NFTCollectionData>(),
612 Type<MetadataViews.NFTCollectionDisplay>(),
613 Type<MetadataViews.EVMBridgedMetadata>()
614 ]
615 }
616
617
618 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
619 switch viewType {
620 case Type<MetadataViews.NFTCollectionData>():
621 let collectionData = MetadataViews.NFTCollectionData(
622 storagePath: self.CollectionStoragePath,
623 publicPath: self.CollectionPublicPath,
624 publicCollection: Type<&Flobot.Collection>(),
625 publicLinkedType: Type<&Flobot.Collection>(),
626 createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
627 return <-Flobot.createEmptyCollection(nftType: Type<@Flobot.NFT>())
628 })
629 )
630 return collectionData
631 case Type<MetadataViews.NFTCollectionDisplay>():
632 let media = MetadataViews.Media(
633 file: MetadataViews.HTTPFile(
634 url: "https://images.flovatar.com/logo.svg"
635 ),
636 mediaType: "image/svg+xml"
637 )
638 let mediaBanner = MetadataViews.Media(
639 file: MetadataViews.HTTPFile(
640 url: "https://images.flovatar.com/logo-horizontal.svg"
641 ),
642 mediaType: "image/svg+xml"
643 )
644 return MetadataViews.NFTCollectionDisplay(
645 name: "Flovatar Flobot Collection",
646 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.",
647 externalURL: MetadataViews.ExternalURL("https://flovatar.com"),
648 squareImage: media,
649 bannerImage: mediaBanner,
650 socials: {
651 "twitter": MetadataViews.ExternalURL("https://x.com/flovatar"),
652 "discord": MetadataViews.ExternalURL("https://discord.gg/flovatar"),
653 "instagram": MetadataViews.ExternalURL("https://instagram.com/flovatar_nft"),
654 "tiktok": MetadataViews.ExternalURL("https://www.tiktok.com/@flovatar")
655 }
656 )
657 case Type<MetadataViews.EVMBridgedMetadata>():
658 // Implementing this view gives the project control over how the bridged NFT is represented as an ERC721
659 // when bridged to EVM on Flow via the public infrastructure bridge.
660
661 // Compose the contract-level URI. In this case, the contract metadata is located on some HTTP host,
662 // but it could be IPFS, S3, a data URL containing the JSON directly, etc.
663 return MetadataViews.EVMBridgedMetadata(
664 name: "Flobot",
665 symbol: "XMPL",
666 uri: MetadataViews.URI(
667 baseURI: nil, // setting baseURI as nil sets the given value as the uri field value
668 value: "https://example-nft.onflow.org/contract-metadata.json"
669 )
670 )
671 }
672 return nil
673 }
674
675
676
677 // This struct is used to send a data representation of the Flobots
678 // when retrieved using the contract helper methods outside the collection.
679 access(all)
680 struct FlobotData{
681 access(all)
682 let id: UInt64
683
684 access(all)
685 let name: String
686
687 access(all)
688 let metadata: Flobot.Metadata
689
690 access(all)
691 let backgroundId: UInt64?
692
693 access(all)
694 let bio:{ String: String}
695
696 init(id: UInt64, name: String, metadata: Flobot.Metadata, backgroundId: UInt64?, bio:{ String: String}){
697 self.id = id
698 self.name = name
699 self.metadata = metadata
700 self.backgroundId = backgroundId
701 self.bio = bio
702 }
703 }
704
705 // This function will look for a specific Flobot on a user account and return a FlobotData if found
706 access(all)
707 fun getFlobot(address: Address, flobotId: UInt64): FlobotData?{
708
709 let account = getAccount(address)
710 if let flobotCollection = account.capabilities.borrow<&Flobot.Collection>(Flobot.CollectionPublicPath){
711 if let flobot = flobotCollection.borrowFlobot(id: flobotId){
712 return FlobotData(id: flobotId, name: flobot.getName(), metadata: flobot.getMetadata(), backgroundId: flobot.getBackground(), bio: flobot.getBio())
713 }
714 }
715
716 return nil
717 }
718
719 // This function will return all Flobots on a user account and return an array of FlobotData
720 access(all)
721 fun getFlobots(address: Address): [FlobotData]{
722 var flobotData: [FlobotData] = []
723
724 let account = getAccount(address)
725 if let flobotCollection = account.capabilities.borrow<&Flobot.Collection>(Flobot.CollectionPublicPath){
726 for id in flobotCollection.getIDs(){
727 var flobot = flobotCollection.borrowFlobot(id: id)
728 flobotData.append(FlobotData(id: id, name: (flobot!).getName(), metadata: (flobot!).getMetadata(), backgroundId: (flobot!).getBackground(), bio: (flobot!).getBio()))
729 }
730 }
731
732 return flobotData
733 }
734
735 // This returns all the previously minted combinations, so that duplicates won't be allowed
736 access(all)
737 fun getMintedCombinations(): [String]{
738 return Flobot.mintedCombinations.keys
739 }
740
741 // This returns all the previously minted names, so that duplicates won't be allowed
742 access(all)
743 fun getMintedNames(): [String]{
744 return Flobot.mintedNames.keys
745 }
746
747 // This function will add a minted combination to the array
748 access(account)
749 fun addMintedCombination(combination: String){
750 Flobot.mintedCombinations.insert(key: combination, true)
751 }
752
753 // This function will add a new name to the array
754 access(account)
755 fun addMintedName(name: String){
756 Flobot.mintedNames.insert(key: name, true)
757 }
758
759 // This helper function will generate a string from a list of components,
760 // to be used as a sort of barcode to keep the inventory of the minted
761 // Flobots and to avoid duplicates
762 access(all)
763 fun getCombinationString(body: UInt64, head: UInt64, arms: UInt64, legs: UInt64, face: UInt64): String{
764 return "B".concat(body.toString()).concat("H").concat(head.toString()).concat("A").concat(arms.toString()).concat("L").concat(legs.toString()).concat("F").concat(face.toString())
765 }
766
767 // This function will get a list of component IDs and will check if the
768 // generated string is unique or if someone already used it before.
769 access(all)
770 fun checkCombinationAvailable(body: UInt64, head: UInt64, arms: UInt64, legs: UInt64, face: UInt64): Bool{
771 let combinationString = Flobot.getCombinationString(body: body, head: head, arms: arms, legs: legs, face: face)
772 return !Flobot.mintedCombinations.containsKey(combinationString)
773 }
774
775 // This will check if a specific Name has already been taken
776 // and assigned to some Flobot
777 access(all)
778 fun checkNameAvailable(name: String): Bool{
779 return name.length > 2 && name.length < 20 && !Flobot.mintedNames.containsKey(name)
780 }
781
782 // This is a public function that anyone can call to generate a new Flobot
783 // A list of components resources needs to be passed to executed.
784 // It will check first for uniqueness of the combination + name and will then
785 // generate the Flobot and burn all the passed components.
786 // The Spark NFT will entitle to use any common basic component (body, hair, etc.)
787 // In order to use special rare components a boost of the same rarity will be needed
788 // for each component used
789 access(all)
790 fun createFlobot(flobotkit: @[FlovatarComponent.NFT], body: UInt64, head: UInt64, arms: UInt64, legs: UInt64, face: UInt64, background: @FlovatarComponent.NFT?, address: Address): @Flobot.NFT{
791 var i: Int = 0
792 var flobotkitSeries: UInt32 = 0
793 var flobotkitRarity: String = ""
794 var checkFlobotRarity: Bool = false
795 var checkFlobotSeries: Bool = false
796 while i < flobotkit.length{
797 if flobotkit[i].getCategory() != "flobotkit"{
798 panic("The Flobot Kit belongs to the wrong category")
799 }
800 if flobotkit[i].getSeries() != 2 {
801 panic("The Flobot Kit doesn't belong to the correct series")
802 }
803 if flobotkitRarity != flobotkit[i].getRarity(){
804 if flobotkitRarity != ""{
805 checkFlobotRarity = true
806 }
807 flobotkitRarity = flobotkit[i].getRarity()
808 }
809 if flobotkitSeries != flobotkit[i].getSeries(){
810 if flobotkitSeries != 0 {
811 checkFlobotSeries = true
812 }
813 flobotkitSeries = flobotkit[i].getSeries()
814 }
815 i = i + 1
816 }
817 if checkFlobotRarity{
818 panic("The Flobot Kits need to belong to the same Rarity level")
819 }
820 if checkFlobotSeries{
821 panic("The Flobot Kits need to belong to the same Series")
822 }
823 if flobotkit.length != 1 && flobotkit.length != 5{
824 panic("You need to pass either 1 Flobot Kit or 5 of them to access the next rarity level")
825 }
826 if flobotkit.length == 5{
827 if flobotkitRarity == "common"{
828 flobotkitRarity = "epic"
829 } else if flobotkitRarity == "epic"{
830 flobotkitRarity = "legendary"
831 } else{
832 panic("Impossible to upgrade the Rarity level for the Flobot Kit")
833 }
834 }
835 let bodyTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: body)!
836 let headTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: head)!
837 let armsTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: arms)!
838 let legsTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: legs)!
839 let faceTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: face)!
840
841 // Make sure that all components belong to the correct category
842 if bodyTemplate.category != "body"{
843 panic("The body component belongs to the wrong category")
844 }
845 if headTemplate.category != "head"{
846 panic("The head component belongs to the wrong category")
847 }
848 if armsTemplate.category != "arms"{
849 panic("The arms component belongs to the wrong category")
850 }
851 if legsTemplate.category != "legs"{
852 panic("The legs component belongs to the wrong category")
853 }
854 if faceTemplate.category != "face"{
855 panic("The face component belongs to the wrong category")
856 }
857
858 // Make sure that all the components belong to the same series like the flobotkit
859 if bodyTemplate.series != flobotkitSeries{
860 panic("The body doesn't belong to the correct series")
861 }
862 if headTemplate.series != flobotkitSeries{
863 panic("The head doesn't belong to the correct series")
864 }
865 if armsTemplate.series != flobotkitSeries{
866 panic("The arms doesn't belong to the correct series")
867 }
868 if legsTemplate.series != flobotkitSeries{
869 panic("The legs doesn't belong to the correct series")
870 }
871 if faceTemplate.series != flobotkitSeries{
872 panic("The face doesn't belong to the correct series")
873 }
874 var flobotRarity: String = "common"
875 if bodyTemplate.rarity == "rare"{
876 flobotRarity = "rare"
877 }
878 if headTemplate.rarity == "rare"{
879 flobotRarity = "rare"
880 }
881 if armsTemplate.rarity == "rare"{
882 flobotRarity = "rare"
883 }
884 if legsTemplate.rarity == "rare"{
885 flobotRarity = "rare"
886 }
887 if faceTemplate.rarity == "rare"{
888 flobotRarity = "rare"
889 }
890 if bodyTemplate.rarity == "epic"{
891 flobotRarity = "epic"
892 }
893 if headTemplate.rarity == "epic"{
894 flobotRarity = "epic"
895 }
896 if armsTemplate.rarity == "epic"{
897 flobotRarity = "epic"
898 }
899 if legsTemplate.rarity == "epic"{
900 flobotRarity = "epic"
901 }
902 if faceTemplate.rarity == "epic"{
903 flobotRarity = "epic"
904 }
905 if bodyTemplate.rarity == "legendary"{
906 flobotRarity = "legendary"
907 }
908 if headTemplate.rarity == "legendary"{
909 flobotRarity = "legendary"
910 }
911 if armsTemplate.rarity == "legendary"{
912 flobotRarity = "legendary"
913 }
914 if legsTemplate.rarity == "legendary"{
915 flobotRarity = "legendary"
916 }
917 if faceTemplate.rarity == "legendary"{
918 flobotRarity = "legendary"
919 }
920 if background != nil{
921 if background?.getSeries() != 1 && !background?.checkCategorySeries(category: "background", series: flobotkitSeries)!{
922 panic("The background component belongs to the wrong category or the wrong series")
923 }
924 }
925 if flobotRarity != flobotkitRarity{
926 if flobotRarity == "rare" && flobotkitRarity == "common" || flobotRarity == "epic" && (flobotkitRarity == "common" || flobotkitRarity == "rare") || flobotRarity == "legendary" && (flobotkitRarity == "common" || flobotkitRarity == "rare" || flobotkitRarity == "epic"){
927 panic("The Rarity of your Flobot Constructor Kit is not high enough")
928 }
929 }
930
931 // Generates the combination string to check for uniqueness.
932 // This is like a barcode that defines exactly which components were used
933 // to create the Flobot
934 let combinationString = Flobot.getCombinationString(body: body, head: head, arms: arms, legs: legs, face: face)
935
936 // Makes sure that the combination is available and not taken already
937 if Flobot.mintedCombinations.containsKey(combinationString) == true{
938 panic("This combination has already been taken")
939 }
940
941 // Creates the metadata for the new Flobot
942 let metadata = Metadata(mint: Flobot.totalSupply + 1, series: flobotkitSeries, combination: combinationString, rarity: flobotRarity, creatorAddress: address, components:{ "body": body, "head": head, "arms": arms, "legs": legs, "face": face})
943 let royalties: [Royalty] = []
944 let creatorAccount = getAccount(address)
945 royalties.append(Royalty(wallet: creatorAccount.capabilities.get<&FlowToken.Vault>(/public/flowTokenReceiver), cut: Flobot.getRoyaltyCut(), type: RoyaltyType.percentage))
946 royalties.append(Royalty(wallet: self.account.capabilities.get<&FlowToken.Vault>(/public/flowTokenReceiver), cut: Flobot.getMarketplaceCut(), type: RoyaltyType.percentage))
947
948 // Mint the new Flobot NFT by passing the metadata to it
949 var newNFT <- create NFT(metadata: metadata, royalties: Royalties(royalty: royalties))
950
951 // Adds the combination to the arrays to remember it
952 Flobot.addMintedCombination(combination: combinationString)
953
954 // Checks for any additional optional component (accessory, hat,
955 // eyeglasses, background) and assigns it to the Flobot if present.
956 if background != nil{
957 let temp <- newNFT.setBackground(component: <-background!)
958 destroy temp
959 } else{
960 destroy background
961 }
962
963 // Emits the Created event to notify about its existence
964 emit Created(id: newNFT.id, metadata: metadata)
965
966 // Destroy all the flobotkit and the rarity boost since they are not needed anymore.
967 destroy flobotkit
968 return <-newNFT
969 }
970
971 // These functions will return the current Royalty cuts for
972 // both the Creator and the Marketplace.
973 access(all)
974 fun getRoyaltyCut(): UFix64{
975 return self.royaltyCut
976 }
977
978 access(all)
979 fun getMarketplaceCut(): UFix64{
980 return self.marketplaceCut
981 }
982
983 // Only Admins will be able to call the set functions to
984 // manage Royalties and Marketplace cuts.
985 access(account)
986 fun setRoyaltyCut(value: UFix64){
987 self.royaltyCut = value
988 }
989
990 access(account)
991 fun setMarketplaceCut(value: UFix64){
992 self.marketplaceCut = value
993 }
994
995 // This is the main Admin resource that will allow the owner
996 // to generate new Templates, Components and Packs
997 access(all)
998 resource Admin{
999
1000 // With this function you can generate a new Admin resource
1001 // and pass it to another user if needed
1002 access(all)
1003 fun createNewAdmin(): @Admin{
1004 return <-create Admin()
1005 }
1006
1007 // Helper functions to update the Royalty cut
1008 access(all)
1009 fun setRoyaltyCut(value: UFix64){
1010 Flobot.setRoyaltyCut(value: value)
1011 }
1012
1013 // Helper functions to update the Marketplace cut
1014 access(all)
1015 fun setMarketplaceCut(value: UFix64){
1016 Flobot.setMarketplaceCut(value: value)
1017 }
1018 }
1019
1020 init(){
1021 self.CollectionPublicPath = /public/FlobotCollection
1022 self.CollectionStoragePath = /storage/FlobotCollection
1023 self.AdminStoragePath = /storage/FlobotAdmin
1024
1025 // Initialize the total supply
1026 self.totalSupply = 0
1027 self.mintedCombinations ={}
1028 self.mintedNames ={}
1029
1030 // Set the default Royalty and Marketplace cuts
1031 self.royaltyCut = 0.01
1032 self.marketplaceCut = 0.05
1033 self.account.storage.save<@{NonFungibleToken.Collection}>(<-Flobot.createEmptyCollection(nftType: Type<@Flobot.Collection>()), to: Flobot.CollectionStoragePath)
1034 var capability_1 = self.account.capabilities.storage.issue<&{Flobot.CollectionPublic}>(Flobot.CollectionStoragePath)
1035 self.account.capabilities.publish(capability_1, at: Flobot.CollectionPublicPath)
1036
1037 // Put the Admin resource in storage
1038 self.account.storage.save<@Admin>(<-create Admin(), to: self.AdminStoragePath)
1039 emit ContractInitialized()
1040 }
1041}
1042