Smart Contract

FlovatarDustCollectible

A.921ea449dffec68a.FlovatarDustCollectible

Valid From

86,753,259

Deployed

1w ago
Feb 15, 2026, 07:48:16 PM UTC

Dependents

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