Smart Contract

Wearables

A.e81193c424cfd3fb.Wearables

Deployed

3d ago
Feb 25, 2026, 12:10:17 AM UTC

Dependents

78 imports
1// SPDX-License-Identifier: MIT
2
3/*
4Welcome to the Wearables contract for Doodles2
5
6A wearable is an equipment that can be equipped  to a Doodle2
7*/
8
9import NonFungibleToken from 0x1d7e57aa55817448
10import FungibleToken from 0xf233dcee88fe0abe
11import MetadataViews from 0x1d7e57aa55817448
12import Templates from 0xe81193c424cfd3fb
13import FindUtils from 0x097bafa4e0b48eef
14import ViewResolver from 0x1d7e57aa55817448
15
16access(all) contract Wearables: NonFungibleToken {
17
18	//Holds the total supply of wearables ever minted
19	access(all) var totalSupply: UInt64
20
21	access(all) event ContractInitialized()
22
23	//emitted when an Wearable is minted either as part of dooplication or later as part of batch minting, note that context and its fields will vary according to when/how it was minted
24	access(all) event Minted(id:UInt64, address:Address, name: String, thumbnail:String, set: String, position: String, template: String, tags:[String], templateId:UInt64, context: {String : String})
25
26	//standard paths in the nft metadata standard
27	access(all) let CollectionStoragePath: StoragePath
28	access(all) let CollectionPublicPath: PublicPath
29
30	// SETS
31
32	// sets is a registry that is stored in the contract for ease of reuse and also to be able to count the mints of a set an retire it
33	access(all) event SetRegistered(id:UInt64, name:String)
34	access(all) event SetRetired(id:UInt64)
35
36	//a registry of sets to group Wearables
37	access(all) let sets: {UInt64 : Set}
38
39	//the definition of a set, a data structure that is Retriable and Editionable
40	access(all) struct Set : Templates.Retirable, Templates.Editionable, Templates.RoyaltyHolder {
41		access(all) let id:UInt64
42		access(all) let name: String
43		access(all) var active:Bool
44		access(all) let royalties: [Templates.Royalty]
45		access(all) let creator : String
46		access(self) let extra: {String : AnyStruct}
47
48		init(id:UInt64, name:String, creator: String, royalties: [Templates.Royalty]) {
49			self.id=id
50			self.name=name
51			self.active=true
52			self.royalties=royalties
53			self.creator=creator
54			self.extra={}
55		}
56
57		access(all) fun getSetType() : String {
58			var classifier=""
59			if let type = self.extra["type"] {
60				classifier=type as! String
61			}
62			return classifier
63		}
64
65		access(all) fun getName() : String {
66			return self.name
67		}
68
69		access(all) fun getCreator() : String {
70			return self.creator
71		}
72
73
74		access(all) fun getClassifier() : String{
75			return "set"
76		}
77
78		access(all) fun getCounterSuffix() : String {
79			return self.getName()
80		}
81
82		access(all) fun getContract() : String {
83			return "wearable"
84		}
85	}
86
87	access(account) fun addSet(_ set: Wearables.Set) {
88		/*
89		pre{
90			!self.sets.containsKey(set.id) : "Set is already registered. Id : ".concat(set.id.toString())
91		}
92		*/
93		emit SetRegistered(id:set.id, name:set.name)
94		self.sets[set.id] = set
95	}
96
97	access(account) fun retireSet(_ id:UInt64) {
98		pre{
99			self.sets.containsKey(id) : "Set does not exist. Id : ".concat(id.toString())
100		}
101		emit SetRetired(id:id)
102		self.sets[id]!.enable(false)
103	}
104
105	// POSITION
106	// a concept that tells where on a Doodle2 a wearable can be equipped.
107
108	access(all) event PositionRegistered(id:UInt64, name:String, classifiers:[String])
109	access(all) event PositionRetired(id:UInt64)
110
111	access(all) let positions: {UInt64 : Position}
112
113	//the definition of a position, a data structure that is Retriable and Editionable
114	access(all) struct Position : Templates.Retirable, Templates.Editionable{
115		access(all) let id:UInt64
116		access(all) let name: String
117		access(all) let classifiers: [String]
118		access(all) var active:Bool
119		access(self) let extra: {String : AnyStruct}
120
121		init(id:UInt64, name:String) {
122			self.id=id
123			self.name=name
124			self.classifiers=[]
125			self.active=true
126			self.extra={}
127		}
128
129		access(all) fun getName(_ index: Int) :String {
130			if self.classifiers.length==0 {
131				return self.name
132			}
133
134			let classifier=self.classifiers[index]
135			return self.name.concat("_").concat(classifier)
136		}
137
138		access(all) fun getPositionCount() :Int{
139			let length=self.classifiers.length
140			if length==0 {
141				return 1
142			}
143			return length
144		}
145
146		access(all) fun getClassifier() :String {
147			return "position"
148		}
149
150		access(all) fun getCounterSuffix() : String {
151			return self.name
152		}
153
154		access(all) fun getContract() : String {
155			return "wearable"
156		}
157	}
158
159	access(account) fun addPosition(_ p: Wearables.Position) {
160		/*
161		pre{
162			!self.positions.containsKey(p.id) : "Position is already registered. Id : ".concat(p.id.toString())
163		}
164		*/
165		emit PositionRegistered(id:p.id, name:p.name, classifiers:p.classifiers)
166		self.positions[p.id] = p
167	}
168
169	access(account) fun retirePosition(_ id:UInt64) {
170		pre{
171			self.positions.containsKey(id) : "Position does not exist. Id : ".concat(id.toString())
172		}
173		emit PositionRetired(id:id)
174		self.positions[id]!.enable(false)
175	}
176
177	// TEMPLATE
178	// a template is a preregistered set of values that a Wearable can get when it is minted, it is then copied into the NFT for provenance
179
180	//these events are there to track changes internally for registers that are needed to make this contract work
181	access(all) event TemplateRegistered(id:UInt64, set: UInt64, position: UInt64, name: String, tags:[String])
182	access(all) event TemplateRetired(id:UInt64)
183
184	//a registry of templates that defines how a Wearable can be minted
185	access(all) let templates: {UInt64 : Template}
186
187	//the definition of a template, a data structure that is Retriable and Editionable
188	access(all) struct Template : Templates.Retirable, Templates.Editionable {
189		access(all) let id:UInt64
190		access(all) let name: String
191		access(all) let set: UInt64
192		access(all) let position: UInt64
193		access(all) let tags: [Tag]
194		access(all) let thumbnail: MetadataViews.Media
195
196		//this field is not in use at the moment
197		access(all) let image: MetadataViews.Media
198		access(all) var active: Bool
199		access(all) let hidden: Bool
200		access(all) let plural: Bool
201		access(self) let extra: {String : AnyStruct}
202
203		init(id:UInt64, set: UInt64, position: UInt64, name: String, tags:[Tag], thumbnail: MetadataViews.Media, image: MetadataViews.Media, hidden: Bool, plural: Bool) {
204			self.id=id
205			self.set=set
206			self.position=position
207			self.name=name
208
209			//the first tag should be prefixed to template name for the name of the wearable, the other tags are for labling
210			self.tags=tags
211			self.thumbnail=thumbnail
212			self.image=image
213			self.active=true
214			self.plural=plural
215			self.hidden=hidden
216			self.extra={}
217		}
218
219		access(all) fun getTags() : [String] {
220			let t : [String] = []
221			for tag in self.tags {
222				t.append(tag.getCounterSuffix())
223			}
224			return t
225		}
226
227		//The Layer this template has in the svg logic
228		access(all) fun getLayer() : String {
229			if let layer = self.extra["layer"] {
230				return layer as! String
231			}
232			return ""
233		}
234
235		access(all) fun getPlural() : Bool {
236			return self.plural
237		}
238
239		access(all) fun getHidden() : Bool {
240				return self.hidden
241		}
242
243		access(all) fun getIdentifier() : String {
244			return self.name
245		}
246
247		access(all) fun getClassifier() :String {
248			return "template"
249		}
250
251		// Trim is not a unique identifier here
252		access(all) fun getCounterSuffix() : String {
253			return self.name
254		}
255
256		access(all) fun getContract() : String {
257			return "wearable"
258		}
259
260		access(all) fun getPositionName(_ index:Int) : String {
261			return Wearables.positions[self.position]!.getName(index)
262		}
263
264		access(all) fun getPosition() : Wearables.Position {
265			return Wearables.positions[self.position]!
266		}
267
268		access(all) fun getSetName() : String {
269			return Wearables.sets[self.set]!.getName()
270		}
271
272		access(all) fun getSet() : Wearables.Set {
273			return Wearables.sets[self.set]!
274		}
275
276		access(all) fun getRoyalties() : [MetadataViews.Royalty] {
277			return Wearables.sets[self.set]!.getRoyalties()
278		}
279
280		access(all) fun getExtra() : {String : AnyStruct} {
281			return self.extra
282		}
283
284		access(contract) fun createTagEditionInfo(_ edition: [UInt64]?) : [Templates.EditionInfo] {
285			let editions : [Templates.EditionInfo] = []
286			for i, t in self.tags {
287				var ediNumber : UInt64? = nil
288				if let e = edition {
289					ediNumber = e[i]
290				}
291				editions.append(t.createEditionInfo(ediNumber))
292			}
293			return editions
294		}
295
296		access(all) fun getDescription() : String? {
297			return self.extra["description"] as! String?
298		}
299
300		access(contract) fun setDescription(_ description: String) {
301			self.extra["description"] = description
302		}
303	}
304
305	//A tag is a label for a Wearable, they can have many tags associated with them
306	access(all) struct Tag : Templates.Editionable {
307		access(all) let value : String
308		access(self) let extra: {String : AnyStruct}
309
310		init(value: String) {
311			self.value=value
312			self.extra={}
313		}
314
315		access(all) fun getValue() : String {
316			return self.value
317		}
318
319		access(all) fun getCounterSuffix() : String {
320			return self.value
321		}
322
323		// e.g. set , position
324		access(all) fun getClassifier() : String {
325			return "tag_".concat(self.value)
326		}
327		// e.g. character, wearable
328		access(all) fun getContract() : String {
329			return "wearable"
330		}
331	}
332
333	access(account) fun addTemplate(_ t: Wearables.Template) {
334		pre{
335			self.sets.containsKey(t.set) : "Set does not exist. Name : ".concat(t.set.toString())
336			self.positions.containsKey(t.position) : "Position does not exist. Name : ".concat(t.position.toString())
337		//	!self.templates.containsKey(t.id) : "Template is already registered. Id : ".concat(t.id.toString())
338		}
339		emit TemplateRegistered(id: t.id, set: t.set, position: t.position, name: t.name, tags: t.getTags())
340		self.templates[t.id] = t
341	}
342
343	access(account) fun retireTemplate(_ id:UInt64) {
344		pre{
345			self.templates.containsKey(id) : "Template does not exist. Name : ".concat(id.toString())
346		}
347		emit TemplateRetired(id: id)
348		self.templates[id]!.enable(false)
349	}
350
351	// NFT
352	// the NFT resource that is a Wearable.
353
354	// A resource on flow https://developers.flow.com/cadence/language/resources is kind of like a struct just with way stronger semantics and rules around security
355
356	access(all) resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver {
357
358		//the unique id of a NFT, Wearables uses UUID so this id is unique across _all_ resources on flow
359		access(all) let id:UInt64
360
361		//The template that this Werable was made as
362		//UPDATE:This template will not be used other then to look up the global template by the id. Since there was errors in dooplicator template data we have to fix.
363		access(all) let template: Template
364
365		//a list of edition info used to present counters for the various types of data
366		//UPDATE: Only the first edition can be used as the others are invalid when metadata is changed unfortunately
367		access(all) let editions: [Templates.EditionInfo]
368
369		//stores who has interacted with this wearable, that
370		access(all) let interactions: [Pointer]
371
372		//internal counter to count how many times a wearable has been deposited.
373		access(account) var nounce:UInt64
374
375		//the royalties defined in this wearable
376		access(all) let royalties: MetadataViews.Royalties
377
378		access(all) let context: { String: String}
379		//stores extra data in case we need it for later iterations since we cannot add data
380		access(all) let extra: {String : AnyStruct}
381
382		init(
383			template: Template,
384			editions: [Templates.EditionInfo],
385			context: {String:String}
386		) {
387			self.template=template
388			self.interactions=[]
389			self.nounce=0
390			self.id=self.uuid
391			self.royalties=MetadataViews.Royalties(template.getRoyalties())
392			self.context=context
393			self.extra={}
394			self.editions=editions
395		}
396
397		access(all) fun getContext() : {String:String} {
398			return self.context
399		}
400
401		access(all) view fun getViews(): [Type] {
402			return  [
403			Type<MetadataViews.Display>(),
404			Type<MetadataViews.Royalties>(),
405			Type<MetadataViews.ExternalURL>(),
406			Type<MetadataViews.NFTCollectionData>(),
407			Type<MetadataViews.NFTCollectionDisplay>(),
408			Type<MetadataViews.Traits>(),
409			Type<MetadataViews.Editions>(),
410			Type<Wearables.Metadata>()
411			]
412		}
413
414		access(all) fun getTemplateActive() : Bool {
415			return self.getTemplate().active
416		}
417
418		access(all) fun getPositionActive() : Bool {
419			let p = self.getTemplate().getPosition()
420			return p.active
421		}
422
423		access(all) fun getSetActive() : Bool {
424			let s = self.getTemplate().getSet()
425			return s.active
426		}
427
428		access(all) fun getTemplate() : Template {
429			return Wearables.templates[self.template.id]!
430		}
431
432		access(all) fun getActive(_ classifier: String) : Bool {
433			switch classifier {
434
435				case "wearable" :
436					return self.getTemplateActive()
437
438				case "template" :
439					return self.getTemplateActive()
440
441				case "position" :
442					return self.getPositionActive()
443
444				case "set" :
445					return self.getSetActive()
446			}
447			return true
448		}
449
450		access(all) fun getLastInteraction() : Pointer? {
451			if self.interactions.length == 0 {
452				return nil
453			}
454			return self.interactions[self.interactions.length - 1]
455		}
456
457		access(account) fun equipped(owner: Address, characterId: UInt64) {
458			if let lastInteraction = self.getLastInteraction() {
459				if !lastInteraction.isNewInteraction(owner: owner, characterId: characterId) {
460					return
461				}
462			}
463			let interaction = Pointer(id: self.id, characterId: characterId, address: owner)
464			self.interactions.append(interaction)
465		}
466
467		//the thumbnail is a png but the image is a SVG, it was decided after deployment that the svg is what we will use for thumbnail and ignore the image
468		//UPDATE: since we now refer to template we will fix all to use the propper thumbnail
469		access(all) fun getThumbnail() : {MetadataViews.File} {
470				return self.getTemplate().thumbnail.file as! MetadataViews.IPFSFile
471		}
472
473		access(all) fun getThumbnailUrl() : String{
474			return self.getThumbnail().uri()
475		}
476
477
478		access(all) fun getName() : String {
479			let template=self.getTemplate()
480			if template.tags.length == 0 {
481				return template.name
482			}
483			let firstTag = template.tags[0].value
484			let name=firstTag.concat(" ").concat(template.name)
485			return name
486		}
487
488
489		access(all) fun getDescription() : String {
490			let description = self.getTemplate().getDescription()
491			
492			if description != nil {
493				return description!
494			}
495
496			var plural=self.getTemplate().getPlural()
497
498			var first="This"
499			var second="is"
500			if plural {
501				first="These"
502				second="are"
503			}
504
505			return first.concat(" ").concat(self.getName()).concat(" ").concat(second).concat(" from the Doodles ").concat(self.getTemplate().getSetName()).concat(" collection.")
506
507		}
508
509		access(all) fun resolveView(_ view: Type): AnyStruct? {
510			let description= self.getDescription()
511
512			switch view {
513			case Type<MetadataViews.Display>():
514				return MetadataViews.Display(
515					name: self.getName(),
516					description: description,
517					thumbnail: self.getThumbnail(),
518				)
519
520			case Type<MetadataViews.ExternalURL>():
521
522				var networkPrefix=""
523				if Wearables.account.address.toString() ==  "0x1c5033ad60821c97" {
524					networkPrefix="test."
525				}
526				return MetadataViews.ExternalURL("https://".concat(networkPrefix).concat("find.xyz/").concat(self.owner!.address.toString()).concat("/collection/main/Wearables/").concat(self.id.toString()))
527
528			case Type<MetadataViews.Royalties>():
529				let royalties =self.royalties
530				let royalty=royalties.getRoyalties()[0]
531
532				let doodlesMerchantAccountMainnet="0x014e9ddc4aaaf557"
533				//royalties if we sell on something else then DapperWallet cannot go to the address stored in the contract, and Dapper will not allow us to setup forwarders for Flow/USDC
534				if royalty.receiver.address.toString() == doodlesMerchantAccountMainnet {
535
536					//this is an account that have setup a forwarder for DUC/FUT to the merchant account of Doodles.
537					let royaltyAccountWithDapperForwarder = getAccount(0x12be92985b852cb8)
538					let cap = royaltyAccountWithDapperForwarder.capabilities.get<&{FungibleToken.Receiver}>(/public/fungibleTokenSwitchboardPublic)
539					return MetadataViews.Royalties([MetadataViews.Royalty(receiver:cap!, cut: royalty.cut, description:royalty.description)])
540				}
541
542				let doodlesMerchanAccountTestnet="0xd5b1a1553d0ed52e"
543				if royalty.receiver.address.toString() == doodlesMerchanAccountTestnet {
544					//on testnet we just send this to the main vault, it is not important
545					let cap = Wearables.account.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
546					return MetadataViews.Royalties([MetadataViews.Royalty(receiver:cap!, cut: royalty.cut, description:royalty.description)])
547				}
548
549				return royalties
550
551			case Type<MetadataViews.NFTCollectionDisplay>():
552				return Wearables.resolveContractView(resourceType: Type<@Wearables.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
553
554			case Type<MetadataViews.NFTCollectionData>():
555				return Wearables.resolveContractView(resourceType: Type<@Wearables.NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
556
557			case Type<MetadataViews.Editions>() :
558
559				let edition=self.editions[0]
560				let active = self.getActive(edition.name)
561				let editions : [MetadataViews.Edition] =[
562					edition.getAsMetadataEdition(active)
563				]
564				return MetadataViews.Editions(editions)
565			case Type<MetadataViews.Traits>():
566				return MetadataViews.Traits(self.getAllTraitsMetadata())
567
568			case Type<Wearables.Metadata>():
569				return Metadata(templateId: self.template.id, setId:self.getTemplate().set, positionId: self.getTemplate().position)
570			}
571
572			return nil
573		}
574
575		access(account) fun increaseNounce() {
576			self.nounce=self.nounce+1
577		}
578
579		access(all) fun getAllTraitsMetadata() : [MetadataViews.Trait] {
580			let template=self.getTemplate()
581			var rarity : MetadataViews.Rarity? = nil
582
583			let traits : [MetadataViews.Trait]= []
584
585			traits.append(MetadataViews.Trait(
586				name: "name",
587				value: self.getName(),
588					displayType: "string",
589				rarity: rarity
590			))
591
592			traits.append(MetadataViews.Trait(
593				name: "template",
594				value: template.name,
595					displayType: "string",
596				rarity: rarity
597			))
598
599			traits.append(MetadataViews.Trait(
600				name: "template_id",
601				value: self.template.id,
602				displayType: "number",
603				rarity: rarity
604			))
605
606			traits.append(MetadataViews.Trait(
607				name: "position",
608				value: template.getPosition().name,
609					displayType: "string",
610				rarity: rarity
611			))
612
613			let layer=self.getTemplate().getLayer()
614			if layer!="" {
615				traits.append(MetadataViews.Trait(
616					name: "layer",
617					value: layer,
618						displayType: "string",
619					rarity: rarity
620				))
621			}
622
623			traits.append(MetadataViews.Trait(
624				name: "set",
625				value: template.getSetName(),
626				displayType: "string",
627				rarity: rarity
628			))
629
630			let setType =template.getSet().getSetType()
631			if setType!="" {
632				traits.append( MetadataViews.Trait(
633					name: "set_type",
634					value: setType,
635					displayType: "string",
636					rarity: rarity
637				)
638			)
639
640			}
641			traits.append( MetadataViews.Trait(
642					name: "set_creator",
643					value: template.getSet().getCreator(),
644					displayType: "string",
645					rarity: rarity
646				)
647			)
648			let edition=self.editions[0]
649			let active = self.getActive(edition.name)
650			var editionStatus="retired"
651			if active {
652				editionStatus="active"
653			}
654			traits.append( MetadataViews.Trait(
655					name: "wearable_status",
656					value: editionStatus,
657					displayType: "string",
658					rarity: rarity
659				)
660			)
661
662			if self.interactions.length == 0 {
663				traits.append(MetadataViews.Trait(name: "condition", value: "mint", displayType:"string", rarity:nil))
664			}
665			// Add tags as traits
666			let tags = template.tags
667			for tag in tags {
668				traits.append(MetadataViews.Trait(name: "tag", value: tag.value, displayType:"string", rarity:nil))
669			}
670
671			let ctx = self.getContext()
672			for key in ctx.keys{
673				let traitKey ="context_".concat(key)
674				traits.append(MetadataViews.Trait(name:traitKey, value: ctx[key], displayType:"string", rarity:nil))
675			}
676			traits.append(MetadataViews.Trait(name:"license", value:"https://doodles.app/terms", displayType:"string", rarity:nil))
677			return traits
678		}
679
680		access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
681            return <-Wearables.createEmptyCollection(nftType: Type<@Wearables.NFT>())
682        }
683	}
684
685	//A metadata for technical information that is not useful as traits
686	access(all) struct Metadata {
687		access(all) let templateId:UInt64
688		access(all) let setId:UInt64
689		access(all) let positionId:UInt64
690
691		init(templateId:UInt64, setId:UInt64, positionId:UInt64) {
692			self.templateId=templateId
693			self.setId=setId
694			self.positionId=positionId
695		}
696	}
697
698	// POINTER
699
700	// a struct to store who has interacted with a wearable
701	access(all) struct Pointer {
702		access(all) let id: UInt64
703		access(all) let characterId: UInt64
704		access(all) let address: Address
705		access(self) let extra: {String : AnyStruct}
706		init(id: UInt64 , characterId: UInt64 , address: Address )  {
707			self.id = id
708			self.characterId = characterId
709			self.address = address
710			self.extra = {}
711		}
712
713		access(all) fun isNewInteraction(owner: Address, characterId: UInt64) : Bool {
714			return self.address == owner && self.characterId == characterId
715		}
716	}
717
718	access(all) resource Collection: NonFungibleToken.Collection  {
719		// dictionary of NFT conforming tokens
720		// NFT is a resource type with an `UInt64` ID field
721		access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
722
723		init () {
724			self.ownedNFTs <- {}
725		}
726
727		// withdraw removes an NFT from the collection and moves it to the caller
728		access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
729			let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
730
731			return <-token
732		}
733
734		// deposit moves an NFT into this collection
735		access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
736			let token <- token as! @NFT
737
738			let id: UInt64 = token.id
739
740			token.increaseNounce()
741			let oldToken <- self.ownedNFTs[id] <- token
742
743
744			destroy oldToken
745		}
746
747		// getIDs returns an array of the IDs that are in the collection
748		access(all) view fun getIDs(): [UInt64] {
749			return self.ownedNFTs.keys
750		}
751
752		// borrowNFT gets a reference to an NFT in the collection
753		// so that the caller can read its metadata and call its methods
754		access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
755			return &self.ownedNFTs[id]
756		}
757
758		//a borrow method for the generic view resolver pattern
759		access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
760			if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? as! &Wearables.NFT? {
761                return nft
762            }
763            return nil
764		}
765
766		//This function is here so that other accounts in the doodles ecosystem can borrow it to perform cross-contract interactions. like bumping the equipped counter
767		access(account) fun borrowWearableNFT(id: UInt64) : &Wearables.NFT? {
768			return &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? as! &Wearables.NFT? 
769		}
770
771		access(all) view fun getLength(): Int {
772            return self.ownedNFTs.keys.length
773        }
774
775        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
776            let supportedTypes: {Type: Bool} = {}
777            supportedTypes[Type<@Wearables.NFT>()] = true
778            return supportedTypes
779        }
780
781        access(all) view fun isSupportedNFTType(type: Type): Bool {
782           if type == Type<@Wearables.NFT>() {
783        	return true
784           } else {
785            return false
786           }
787		}
788
789		access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
790            return <-Wearables.createEmptyCollection(nftType: Type<@Wearables.NFT>())
791        }
792	}
793
794	// public function that anyone can call to create a new empty collection
795	access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
796		return <- create Collection()
797	}
798
799	// mintNFT mints a new NFT with a new ID
800	// and deposit it in the recipients collection using their collection reference
801	//The distinction between sending in a reference and sending in a capability is that when you send in a reference it cannot be stored. So it can only be used in this method
802	//while a capability can be stored and used later. So in this case using a reference is the right choice, but it needs to be owned so that you can have a good event
803	access(account) fun mintNFT(
804		recipient: &{NonFungibleToken.Receiver},
805		template: UInt64,
806		context: {String:String}
807	){
808		pre {
809			recipient.owner != nil : "Recipients NFT collection is not owned"
810		}
811
812		let newNFT <- Wearables.mintNFTDirect(
813			recipientAddress: recipient.owner!.address,
814			template: template,
815			context: context
816		)
817
818		recipient.deposit(token: <-newNFT)
819	}
820
821	// Mint an NFT and return it as a resource. This gives flexibility to mint without depositing it in a collection.
822	// A use case is to mint a Wearable an equip it directly in a Doodle.
823	access(account) fun mintNFTDirect(
824		recipientAddress: Address,
825		template: UInt64,
826		context: {String: String}
827	): @Wearables.NFT {
828		pre {
829			self.templates.containsKey(template) : "Template does not exist. Id : ".concat(template.toString())
830		}
831
832		Wearables.totalSupply = Wearables.totalSupply + 1
833		let template = Wearables.borrowTemplate(template)
834		let set = Wearables.borrowSet(template.set)
835		let position =  Wearables.borrowPosition(template.position)
836
837		assert(set.active, message: "Set Retired : ".concat(set.name))
838		assert(position.active, message: "Position Retired : ".concat(position.name))
839		assert(template.active, message: "Template Retired : ".concat(template.name))
840
841		let tagEditions = template.createTagEditionInfo(nil)
842
843		let editions=[
844			Templates.createEditionInfoManually(name:"wearable", counter:"template_".concat(template.id.toString()), edition:nil),
845		  	template.createEditionInfo(nil),
846			position.createEditionInfo(nil),
847			set.createEditionInfo(nil)
848		]
849
850		editions.appendAll(tagEditions)
851
852		// create a new NFT
853		var newNFT <- create NFT(
854			template: Wearables.templates[template.id]!,
855			editions:editions,
856			context: context
857		)
858
859		emit Minted(id: newNFT.id, address: recipientAddress, name: newNFT.getName(), thumbnail: newNFT.getThumbnailUrl(), set: set.name, position: position.name, template: template.name, tags: template.getTags(), templateId:template.id, context: context)
860
861		return <- newNFT
862	}
863
864	// This code is not active at this point but is here for later
865	// TODO: also send in wearable edition and see mintNFT for info
866	// minteditionNFT mints a new NFT with a manual input in edition
867	// and deposit it in the recipients collection using their collection reference
868	access(account) fun mintEditionNFT(
869		recipient: &{NonFungibleToken.Receiver},
870		template: UInt64,
871		setEdition: UInt64,
872		positionEdition: UInt64,
873		templateEdition: UInt64,
874		taggedTemplateEdition: UInt64,
875		tagEditions: [UInt64],
876		context: {String:String}
877	){
878		pre {
879			recipient.owner != nil : "Recipients NFT collection is not owned"
880			self.templates.containsKey(template) : "Template does not exist. Id : ".concat(template.toString())
881		}
882
883		Wearables.totalSupply = Wearables.totalSupply + 1
884		let template = Wearables.borrowTemplate(template)
885		let set = Wearables.borrowSet(template.set)
886		let position =  Wearables.borrowPosition(template.position)
887
888		// This will only be ran by admins, do we need to assert here?
889		// assert(set.active, message: "Set Retired : ".concat(set.name))
890		// assert(position.active, message: "Position Retired : ".concat(position.name))
891		// assert(template.active, message: "Template Retired : ".concat(template.name))
892
893		let tagEditions = template.createTagEditionInfo(tagEditions)
894
895
896		let editions=[
897			Templates.createEditionInfoManually(name:"wearable", counter:"template_".concat(template.id.toString()), edition:taggedTemplateEdition),
898			template.createEditionInfo(templateEdition),
899			position.createEditionInfo(positionEdition),
900			set.createEditionInfo(setEdition)
901		]
902
903		editions.appendAll(tagEditions)
904
905		// create a new NFT
906		var newNFT <- create NFT(
907			template: Wearables.templates[template.id]!,
908			editions:editions,
909			context: context
910		)
911
912
913		emit Minted(id:newNFT.id, address:recipient.owner!.address, name: newNFT.getName(), thumbnail: newNFT.getThumbnailUrl(), set: set.name, position: position.name, template: template.name, tags: template.getTags(), templateId:template.id, context: context)
914		recipient.deposit(token: <-newNFT)
915
916	}
917
918	access(account) fun borrowSet(_ id: UInt64) : &Wearables.Set {
919		pre{
920			self.sets.containsKey(id) : "Set does not exist. Id : ".concat(id.toString())
921		}
922		return &Wearables.sets[id]!
923	}
924
925	access(account) fun borrowPosition(_ id: UInt64) : &Wearables.Position {
926		pre{
927			self.positions.containsKey(id) : "Position does not exist. Id : ".concat(id.toString())
928		}
929		return &Wearables.positions[id]!
930	}
931
932	access(account) fun borrowTemplate(_ id: UInt64) : &Wearables.Template {
933		pre{
934			self.templates.containsKey(id) : "Template does not exist. Id : ".concat(id.toString())
935		}
936		return &Wearables.templates[id]!
937	}
938
939	access(account) fun updateTemplateDescription(templateId: UInt64, description: String) {
940		pre{
941			self.templates.containsKey(templateId) : "Template  does not exist. Id : ".concat(templateId.toString())
942		}
943		let t = self.templates[templateId]!
944		t.setDescription(description)
945		self.templates[t.id] = t
946	}
947
948	access(all) fun getTemplateCicrulationSupply(templateId:UInt64) :UInt64{
949		return Templates.getCounter("template_".concat(templateId.toString()))
950	}
951
952	//Below here are internal resources that is not really relevant to the public
953
954	//internal struct to use for batch minting that points to a specific wearable
955	access(all) struct WearableMintData {
956		access(all) let template: UInt64
957		access(all) let setEdition: UInt64
958		access(all) let positionEdition: UInt64
959		access(all) let templateEdition: UInt64
960		access(all) let taggedTemplateEdition: UInt64
961		access(all) let tagEditions: [UInt64]
962		access(all) let extra: {String : AnyStruct}
963
964		init(
965			template: UInt64,
966			setEdition: UInt64,
967			positionEdition: UInt64,
968			templateEdition: UInt64,
969			taggedTemplateEdition: UInt64,
970			tagEditions: [UInt64],
971		) {
972			self.template = template
973			self.setEdition = setEdition
974			self.positionEdition = positionEdition
975			self.templateEdition = templateEdition
976			self.taggedTemplateEdition = taggedTemplateEdition
977			self.tagEditions = tagEditions
978			self.extra = {}
979		}
980	}
981
982	// This is not in use anymore. Use WearableMintData
983	//cadence does not allow us to remove this
984	access(all) struct MintData {
985		access(all) let template: UInt64
986		access(all) let setEdition: UInt64
987		access(all) let positionEdition: UInt64
988		access(all) let templateEdition: UInt64
989		access(all) let tagEditions: [UInt64]
990
991		init(
992		) {
993			self.template = 0
994			self.setEdition = 0
995			self.positionEdition = 0
996			self.templateEdition = 0
997			self.tagEditions = [0]
998		}
999	}
1000
1001	access(all) view fun getContractViews(resourceType: Type?): [Type] {
1002        return [
1003            Type<MetadataViews.NFTCollectionDisplay>(),
1004            Type<MetadataViews.NFTCollectionData>()
1005        ]
1006    }
1007
1008    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
1009        switch viewType {
1010            case Type<MetadataViews.NFTCollectionDisplay>():
1011				return MetadataViews.NFTCollectionDisplay(
1012					name: "Wearables", 
1013					description: "Doodles 2 lets anyone create a uniquely personalized and endlessly customizable character in a one-of-a-kind style. Wearables and other collectibles can easily be bought, traded, or sold. Doodles 2 will also incorporate collaborative releases with top brands in fashion, music, sports, gaming, and more.\n\nDoodles 2 Private Beta, which will offer first access to the Doodles character creator tools, will launch later in 2022. Doodles 2 Private Beta will only be available to Beta Pass holders.",
1014					externalURL: MetadataViews.ExternalURL("https://doodles.app"), 
1015					squareImage: MetadataViews.Media(file: MetadataViews.IPFSFile(cid: "QmVpAiutpnzp3zR4q2cUedMxsZd8h5HDeyxs9x3HibsnJb", path:nil), mediaType:"image/png"), 
1016					bannerImage: MetadataViews.Media(file: MetadataViews.IPFSFile(cid: "QmWEsThoSdJHNVcwexYuSucR4MEGhkJEH6NCzdTV71y6GN", path:nil), mediaType:"image/png"), 
1017					socials: { 
1018						"discord": MetadataViews.ExternalURL("https://discord.gg/doodles"), 
1019						"twitter" : MetadataViews.ExternalURL("https://twitter.com/doodles")
1020					})
1021			case Type<MetadataViews.NFTCollectionData>():
1022				return MetadataViews.NFTCollectionData(storagePath: Wearables.CollectionStoragePath,
1023				publicPath: Wearables.CollectionPublicPath,
1024				publicCollection: Type<&Collection>(),
1025				publicLinkedType: Type<&Collection>(),	
1026				createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection}
1027					{return <- Wearables.createEmptyCollection(nftType: Type<@Wearables.NFT>())})
1028				)
1029        }
1030        return nil
1031    }
1032
1033	//setting up the inital state of all the paths and registries
1034	init() {
1035		self.totalSupply = 0
1036
1037		self.sets = {}
1038		self.positions = {}
1039		self.templates = {}
1040		// Set the named paths
1041		self.CollectionStoragePath = /storage/wearables
1042		self.CollectionPublicPath = /public/wearables
1043
1044		self.account.storage.save<@{NonFungibleToken.Collection}>(<- Wearables.createEmptyCollection(nftType: Type<@Wearables.NFT>()), to: Wearables.CollectionStoragePath)
1045		let cap = self.account.capabilities.storage.issue<&Collection>(Wearables.CollectionStoragePath)
1046		self.account.capabilities.publish(cap, at: Wearables.CollectionPublicPath)
1047
1048		emit ContractInitialized()
1049	}
1050}