Smart Contract

Flobot

A.921ea449dffec68a.Flobot

Valid From

86,745,134

Deployed

1w ago
Feb 16, 2026, 11:20:41 PM UTC

Dependents

3401 imports
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