Smart Contract

FlovatarComponent

A.921ea449dffec68a.FlovatarComponent

Valid From

86,477,628

Deployed

2w ago
Feb 11, 2026, 06:35:26 PM UTC

Dependents

3437 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3import FlowToken from 0x1654653399040a61
4import FlovatarComponentTemplate from 0x921ea449dffec68a
5import MetadataViews from 0x1d7e57aa55817448
6import ViewResolver from 0x1d7e57aa55817448
7
8/*
9
10 This contract defines the Flovatar Component NFT and the Collection to manage them.
11 Components are like the building blocks (lego bricks) of the final Flovatar (body, mouth, hair, eyes, etc.) and they can be traded as normal NFTs.
12 Components are linked to a specific Template that will ultimately contain the SVG and all the other metadata
13
14 */
15
16access(all)
17contract FlovatarComponent: NonFungibleToken{ 
18	access(all)
19	let CollectionStoragePath: StoragePath
20	
21	access(all)
22	let CollectionPublicPath: PublicPath
23	
24	// Counter for all the Components ever minted
25	access(all)
26	var totalSupply: UInt64
27	
28	// Standard events that will be emitted
29	access(all)
30	event ContractInitialized()
31	
32	access(all)
33	event Withdraw(id: UInt64, from: Address?)
34	
35	access(all)
36	event Deposit(id: UInt64, to: Address?)
37	
38	access(all)
39	event Created(id: UInt64, templateId: UInt64, mint: UInt64)
40	
41	access(all)
42	event Destroyed(id: UInt64, templateId: UInt64)
43
44	access(all) entitlement PrivateEnt
45	
46	// The public interface provides all the basic informations about
47	// the Component and also the Template ID associated with it.
48	access(all)
49	resource interface Public{ 
50		access(all)
51		let id: UInt64
52		
53		access(all)
54		let templateId: UInt64
55		
56		access(all)
57		let mint: UInt64
58		
59		access(all)
60		fun getTemplate(): FlovatarComponentTemplate.ComponentTemplateData
61		
62		access(all)
63		fun getSvg(): String
64		
65		access(all)
66		fun getCategory(): String
67		
68		access(all)
69		fun getSeries(): UInt32
70		
71		access(all)
72		fun getRarity(): String
73		
74		access(all)
75		fun isBooster(rarity: String): Bool
76		
77		access(all)
78		fun checkCategorySeries(category: String, series: UInt32): Bool
79		
80		//these three are added because I think they will be in the standard. At least Dieter thinks it will be needed
81		access(all)
82		let name: String
83		
84		access(all)
85		let description: String
86		
87		access(all)
88		let schema: String?
89	}
90	
91	// The NFT resource that implements the Public interface as well
92	access(all)
93	resource NFT: NonFungibleToken.NFT, Public{ 
94		access(all)
95		let id: UInt64
96		
97		access(all)
98		let templateId: UInt64
99		
100		access(all)
101		let mint: UInt64
102		
103		access(all)
104		let name: String
105		
106		access(all)
107		let description: String
108		
109		access(all)
110		let schema: String?
111		
112		// Initiates the NFT from a Template ID.
113		init(templateId: UInt64){ 
114			FlovatarComponent.totalSupply = FlovatarComponent.totalSupply + UInt64(1)
115			let componentTemplate = FlovatarComponentTemplate.getComponentTemplate(id: templateId)!
116			self.id = FlovatarComponent.totalSupply
117			self.templateId = templateId
118			self.mint = FlovatarComponentTemplate.getTotalMintedComponents(id: templateId)! + UInt64(1)
119			self.name = componentTemplate.name
120			self.description = componentTemplate.description
121			self.schema = nil
122			
123
124			// Increments the counter and stores the timestamp
125			FlovatarComponentTemplate.setTotalMintedComponents(id: templateId, value: self.mint)
126			FlovatarComponentTemplate.setLastComponentMintedAt(id: templateId, value: getCurrentBlock().timestamp)
127		}
128		
129		access(all)
130		fun getID(): UInt64{ 
131			return self.id
132		}
133		
134		// Returns the Template associated to the current Component
135		access(all)
136		fun getTemplate(): FlovatarComponentTemplate.ComponentTemplateData{ 
137			return FlovatarComponentTemplate.getComponentTemplate(id: self.templateId)!
138		}
139		
140		// Gets the SVG from the parent Template
141		access(all)
142		fun getSvg(): String{ 
143			return self.getTemplate().svg!
144		}
145		
146		// Gets the category from the parent Template
147		access(all)
148		fun getCategory(): String{ 
149			return self.getTemplate().category
150		}
151		
152		// Gets the series number from the parent Template
153		access(all)
154		fun getSeries(): UInt32{ 
155			return self.getTemplate().series
156		}
157		
158		// Gets the rarity from the parent Template
159		access(all)
160		fun getRarity(): String{ 
161			return self.getTemplate().rarity
162		}
163		
164		// Check the boost and rarity from the parent Template
165		access(all)
166		fun isBooster(rarity: String): Bool{ 
167			let template = self.getTemplate()
168			return template.category == "boost" && template.rarity == rarity
169		}
170		
171		//Check the category and series from the parent Template
172		access(all)
173		fun checkCategorySeries(category: String, series: UInt32): Bool{ 
174			let template = self.getTemplate()
175			return template.category == category && template.series == series
176		}
177		
178		// Emit a Destroyed event when it will be burned to create a Flovatar
179		// This will help to keep track of how many Components are still
180		// available on the market.
181		access(all)
182		view fun getViews(): [Type]{ 
183			return [
184			Type<MetadataViews.NFTCollectionData>(),
185			Type<MetadataViews.NFTCollectionDisplay>(),
186			Type<MetadataViews.Display>(),
187			Type<MetadataViews.Royalties>(),
188			Type<MetadataViews.Edition>(),
189			Type<MetadataViews.ExternalURL>(),
190			Type<MetadataViews.Serial>(),
191			Type<MetadataViews.Traits>(),
192			Type<MetadataViews.EVMBridgedMetadata>()
193			]
194		}
195		
196		access(all)
197		fun resolveView(_ view: Type): AnyStruct?{ 
198			if view == Type<MetadataViews.ExternalURL>(){ 
199				let address = self.owner?.address
200				let url = address == nil ? "https://flovatar.com/builder/" : "https://flovatar.com/components/".concat(self.id.toString()).concat("/").concat((address!).toString())
201				return MetadataViews.ExternalURL("https://flovatar.com/builder/")
202			}
203			if view == Type<MetadataViews.Royalties>(){ 
204				let royalties: [MetadataViews.Royalty] = []
205				royalties.append(MetadataViews.Royalty(receiver: FlovatarComponent.account.capabilities.get<&FlowToken.Vault>(/public/flowTokenReceiver), cut: 0.05, description: "Flovatar Royalty"))
206				return MetadataViews.Royalties(royalties)
207			}
208			if view == Type<MetadataViews.Serial>(){ 
209				return MetadataViews.Serial(self.id)
210			}
211			if view == Type<MetadataViews.Editions>(){ 
212				let componentTemplate: FlovatarComponentTemplate.ComponentTemplateData = self.getTemplate()
213				let editionInfo = MetadataViews.Edition(name: "Flovatar Component", number: self.mint, max: componentTemplate.maxMintableComponents)
214				let editionList: [MetadataViews.Edition] = [editionInfo]
215				return MetadataViews.Editions(editionList)
216			}
217			if view == Type<MetadataViews.NFTCollectionDisplay>(){ 
218				let mediaSquare = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://images.flovatar.com/logo.svg"), mediaType: "image/svg+xml")
219				let mediaBanner = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://images.flovatar.com/logo-horizontal.svg"), mediaType: "image/svg+xml")
220				return MetadataViews.NFTCollectionDisplay(name: "Flovatar Component", 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")})
221			}
222			if view == Type<MetadataViews.Display>(){ 
223				return MetadataViews.Display(name: self.name, description: self.description, thumbnail: MetadataViews.HTTPFile(url: "https://flovatar.com/api/image/template/".concat(self.templateId.toString())))
224			}
225			if view == Type<MetadataViews.Traits>(){ 
226				let traits: [MetadataViews.Trait] = []
227				let template = self.getTemplate()
228				let trait = MetadataViews.Trait(name: template.category, value: template.name, displayType: "String", rarity: MetadataViews.Rarity(score: nil, max: nil, description: template.rarity))
229				traits.append(trait)
230				let setTrait = MetadataViews.Trait(name: "set", value: template.category, displayType: "String", rarity: nil)
231				traits.append(setTrait)
232				return MetadataViews.Traits(traits)
233			}
234			if view == Type<MetadataViews.Rarity>(){ 
235				let template = self.getTemplate()
236				return MetadataViews.Rarity(score: nil, max: nil, description: template.rarity)
237			}
238			if view == Type<MetadataViews.NFTCollectionData>(){ 
239				return MetadataViews.NFTCollectionData(storagePath: FlovatarComponent.CollectionStoragePath, publicPath: FlovatarComponent.CollectionPublicPath, publicCollection: Type<&FlovatarComponent.Collection>(), publicLinkedType: Type<&FlovatarComponent.Collection>(), createEmptyCollectionFunction: fun (): @{NonFungibleToken.Collection}{ 
240						return <-FlovatarComponent.createEmptyCollection(nftType: Type<@FlovatarComponent.Collection>())
241					})
242			}
243
244			if view == Type<MetadataViews.EVMBridgedMetadata>(){ 
245				let contractLevel = FlovatarComponent.resolveContractView(
246						resourceType: nil,
247						viewType: Type<MetadataViews.EVMBridgedMetadata>()
248					) as! MetadataViews.EVMBridgedMetadata?
249					?? panic("Could not resolve contract-level EVMBridgedMetadata")
250				
251				return MetadataViews.EVMBridgedMetadata(
252					name: contractLevel.name,
253					symbol: contractLevel.symbol,
254					uri: MetadataViews.URI(
255						baseURI: "https://flovatar.com/components/json/",
256						value: self.id.toString()
257					)
258				)
259			}
260			return nil
261		}
262		
263		access(all)
264		fun createEmptyCollection(): @{NonFungibleToken.Collection}{ 
265			return <-create Collection()
266		}
267	}
268	
269	// Standard NFT collectionPublic interface that can also borrowComponent as the correct type
270	access(all)
271	resource interface CollectionPublic{ 
272		access(all)
273		fun deposit(token: @{NonFungibleToken.NFT})
274		
275		access(all)
276		fun getIDs(): [UInt64]
277		
278		access(all)
279		view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
280
281		access(all)
282		fun borrowComponent(id: UInt64): &FlovatarComponent.NFT?{ 
283			// If the result isn't nil, the id of the returned reference
284			// should be the same as the argument to the function
285			post{ 
286				result == nil || result?.id == id:
287					"Cannot borrow Component reference: The ID of the returned reference is incorrect"
288			}
289		}
290		
291	}
292	
293	// Main Collection to manage all the Components NFT
294	access(all)
295	resource Collection: CollectionPublic, NonFungibleToken.Collection { 
296		// dictionary of NFT conforming tokens
297		// NFT is a resource type with an `UInt64` ID field
298		access(all)
299		var ownedNFTs: @{UInt64:{ NonFungibleToken.NFT}}
300		
301		init(){ 
302			self.ownedNFTs <-{} 
303		}
304		
305		access(all) view fun getLength(): Int {
306			return self.ownedNFTs.length
307		}
308		
309		// withdraw removes an NFT from the collection and moves it to the caller
310		access(NonFungibleToken.Withdraw)
311		fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT}{ 
312			let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
313			emit Withdraw(id: token.id, from: self.owner?.address)
314			return <-token
315		}
316		
317		// deposit takes a NFT and adds it to the collections dictionary
318		// and adds the ID to the id array
319		access(all)
320		fun deposit(token: @{NonFungibleToken.NFT}){ 
321			let token <- token as! @FlovatarComponent.NFT
322			let id: UInt64 = token.id
323			
324			// add the new token to the dictionary which removes the old one
325			let oldToken <- self.ownedNFTs[id] <- token
326			emit Deposit(id: id, to: self.owner?.address)
327			destroy oldToken
328		}
329		
330		// getIDs returns an array of the IDs that are in the collection
331		access(all)
332		view fun getIDs(): [UInt64]{ 
333			return self.ownedNFTs.keys
334		}
335		
336		// borrowNFT gets a reference to an NFT in the collection
337		// so that the caller can read its metadata and call its methods
338		access(all)
339		view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?{ 
340			return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
341		}
342
343		access(all)
344		fun borrowComponent(id: UInt64): &FlovatarComponent.NFT?{ 
345			if self.ownedNFTs[id] != nil {
346				let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
347				let componentNFT = ref as! &FlovatarComponent.NFT
348				return componentNFT
349			} else {
350				return nil
351			}
352		}
353		
354		
355		access(all)
356		view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?{ 
357			if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
358				return nft as &{ViewResolver.Resolver}
359			}
360			return nil
361		}
362		
363		
364
365		/// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
366		access(all) 
367		view fun getSupportedNFTTypes(): {Type: Bool} {
368			let supportedTypes: {Type: Bool} = {}
369			supportedTypes[Type<@FlovatarComponent.NFT>()] = true
370			return supportedTypes
371		}
372
373		/// Returns whether or not the given type is accepted by the collection
374		/// A collection that can accept any type should just return true by default
375		access(all) 
376		view fun isSupportedNFTType(type: Type): Bool {
377			return type == Type<@FlovatarComponent.NFT>()
378		}
379		
380		access(all)
381		fun createEmptyCollection(): @{NonFungibleToken.Collection}{ 
382			return <-FlovatarComponent.createEmptyCollection(nftType: Type<@FlovatarComponent.NFT>())
383		}
384	}
385
386
387
388
389
390	access(all) 
391	view fun getContractViews(resourceType: Type?): [Type] {
392		return [
393			Type<MetadataViews.NFTCollectionData>(),
394			Type<MetadataViews.NFTCollectionDisplay>(),
395			Type<MetadataViews.EVMBridgedMetadata>()
396		]
397	}
398
399
400	access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
401		switch viewType {
402			case Type<MetadataViews.NFTCollectionData>():
403				let collectionData = MetadataViews.NFTCollectionData(
404					storagePath: self.CollectionStoragePath,
405					publicPath: self.CollectionPublicPath,
406					publicCollection: Type<&FlovatarComponent.Collection>(),
407					publicLinkedType: Type<&FlovatarComponent.Collection>(),
408					createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
409						return <-FlovatarComponent.createEmptyCollection(nftType: Type<@FlovatarComponent.NFT>())
410					})
411				)
412				return collectionData
413			case Type<MetadataViews.NFTCollectionDisplay>():
414				let media = MetadataViews.Media(
415					file: MetadataViews.HTTPFile(
416						url: "https://images.flovatar.com/logo.svg"
417					),
418					mediaType: "image/svg+xml"
419				)
420				let mediaBanner = MetadataViews.Media(
421					file: MetadataViews.HTTPFile(
422						url: "https://images.flovatar.com/logo-horizontal.svg"
423					),
424					mediaType: "image/svg+xml"
425				)
426				return MetadataViews.NFTCollectionDisplay(
427					name: "Flovatar Component Collection",
428					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.",
429					externalURL: MetadataViews.ExternalURL("https://flovatar.com"),
430					squareImage: media,
431					bannerImage: mediaBanner,
432					socials: {
433						"twitter": MetadataViews.ExternalURL("https://x.com/flovatar"),
434						"discord": MetadataViews.ExternalURL("https://discord.gg/flovatar"),
435						"instagram": MetadataViews.ExternalURL("https://instagram.com/flovatar_nft"),
436						"tiktok": MetadataViews.ExternalURL("https://www.tiktok.com/@flovatar")
437					}
438				)
439			case Type<MetadataViews.EVMBridgedMetadata>():
440				// Implementing this view gives the project control over how the bridged NFT is represented as an ERC721
441				// when bridged to EVM on Flow via the public infrastructure bridge.
442
443				// Compose the contract-level URI. In this case, the contract metadata is located on some HTTP host,
444				// but it could be IPFS, S3, a data URL containing the JSON directly, etc.
445				return MetadataViews.EVMBridgedMetadata(
446					name: "Flovatar Component",
447					symbol: "FLVC",
448					uri: MetadataViews.URI(
449						baseURI: nil, // setting baseURI as nil sets the given value as the uri field value
450						value: "https://flovatar.com"
451					)
452				)
453		}
454		return nil
455	}
456	
457	// public function that anyone can call to create a new empty collection
458	access(all)
459	fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection}{ 
460		return <-create Collection()
461	}
462	
463	// This struct is used to send a data representation of the Components
464	// when retrieved using the contract helper methods outside the collection.
465	access(all)
466	struct ComponentData{ 
467		access(all)
468		let id: UInt64
469		
470		access(all)
471		let templateId: UInt64
472		
473		access(all)
474		let mint: UInt64
475		
476		access(all)
477		let name: String
478		
479		access(all)
480		let description: String
481		
482		access(all)
483		let category: String
484		
485		access(all)
486		let rarity: String
487		
488		access(all)
489		let color: String
490		
491		init(id: UInt64, templateId: UInt64, mint: UInt64){ 
492			self.id = id
493			self.templateId = templateId
494			self.mint = mint
495			let componentTemplate = FlovatarComponentTemplate.getComponentTemplate(id: templateId)!
496			self.name = componentTemplate.name
497			self.description = componentTemplate.description
498			self.category = componentTemplate.category
499			self.rarity = componentTemplate.rarity
500			self.color = componentTemplate.color
501		}
502	}
503	
504	// Get the SVG of a specific Component from an account and the ID
505	access(all)
506	fun getSvgForComponent(address: Address, componentId: UInt64): String?{ 
507		let account = getAccount(address)
508		if let componentCollection = account.capabilities.borrow<&FlovatarComponent.Collection>(FlovatarComponent.CollectionPublicPath){ 
509			return (componentCollection.borrowComponent(id: componentId)!).getSvg()
510		}
511		
512		return nil
513	}
514	
515	// Get a specific Component from an account and the ID as ComponentData
516	access(all)
517	fun getComponent(address: Address, componentId: UInt64): ComponentData?{ 
518		let account = getAccount(address)
519		if let componentCollection = account.capabilities.borrow<&FlovatarComponent.Collection>(FlovatarComponent.CollectionPublicPath){ 
520			if !componentCollection.isInstance(Type<@FlovatarComponent.Collection>()){ 
521				panic("The Collection is not from the correct Type")
522			}
523			if let component = componentCollection.borrowComponent(id: componentId){ 
524				return ComponentData(id: componentId, templateId: (component!).templateId, mint: (component!).mint)
525			}
526		}
527		
528		return nil
529	}
530	
531	// Get an array of all the components in a specific account as ComponentData
532	access(all)
533	fun getComponents(address: Address): [ComponentData]{ 
534		var componentData: [ComponentData] = []
535		let account = getAccount(address)
536		if let componentCollection = account.capabilities.borrow<&FlovatarComponent.Collection>(FlovatarComponent.CollectionPublicPath){ 
537			if !componentCollection.isInstance(Type<@FlovatarComponent.Collection>()){ 
538				panic("The Collection is not from the correct Type")
539			}
540			for id in componentCollection.getIDs(){ 
541				var component = componentCollection.borrowComponent(id: id)
542				componentData.append(ComponentData(id: id, templateId: (component!).templateId, mint: (component!).mint))
543			}
544		}
545		
546		return componentData
547	}
548	
549	// This method can only be called from another contract in the same account.
550	// In FlovatarComponent case it is called from the Flovatar Admin that is used
551	// to administer the components.
552	// The only parameter is the parent Template ID and it will return a Component NFT resource
553	access(account)
554	fun createComponent(templateId: UInt64): @FlovatarComponent.NFT{ 
555		let componentTemplate: FlovatarComponentTemplate.ComponentTemplateData = FlovatarComponentTemplate.getComponentTemplate(id: templateId)!
556		let totalMintedComponents: UInt64 = FlovatarComponentTemplate.getTotalMintedComponents(id: templateId)!
557		
558		// Makes sure that the original minting limit set for each Template has not been reached
559		if totalMintedComponents >= componentTemplate.maxMintableComponents{ 
560			panic("Reached maximum mintable components for this type")
561		}
562		var newNFT <- create NFT(templateId: templateId)
563		emit Created(id: newNFT.id, templateId: templateId, mint: newNFT.mint)
564		return <-newNFT
565	}
566	
567	// This function will batch create multiple Components and pass them back as a Collection
568	access(account)
569	fun batchCreateComponents(templateId: UInt64, quantity: UInt64): @Collection{ 
570		let newCollection <- create Collection()
571		var i: UInt64 = 0
572		while i < quantity{ 
573			newCollection.deposit(token: <-self.createComponent(templateId: templateId))
574			i = i + UInt64(1)
575		}
576		return <-newCollection
577	}
578	
579	init(){ 
580		self.CollectionPublicPath = /public/FlovatarComponentCollection
581		self.CollectionStoragePath = /storage/FlovatarComponentCollection
582		
583		// Initialize the total supply
584		self.totalSupply = UInt64(0)
585		self.account.storage.save<@{NonFungibleToken.Collection}>(<-FlovatarComponent.createEmptyCollection(nftType: Type<@FlovatarComponent.Collection>()), to: FlovatarComponent.CollectionStoragePath)
586		var capability_1 = self.account.capabilities.storage.issue<&{FlovatarComponent.CollectionPublic}>(FlovatarComponent.CollectionStoragePath)
587		self.account.capabilities.publish(capability_1, at: FlovatarComponent.CollectionPublicPath)
588		emit ContractInitialized()
589	}
590}
591