Smart Contract

Doodles

A.e81193c424cfd3fb.Doodles

Deployed

2d ago
Feb 25, 2026, 01:10:29 AM UTC

Dependents

3455 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import FungibleToken from 0xf233dcee88fe0abe
3import MetadataViews from 0x1d7e57aa55817448
4import Wearables from 0xe81193c424cfd3fb
5import Templates from 0xe81193c424cfd3fb
6import DoodleNames from 0xe81193c424cfd3fb
7import FindUtils from 0x097bafa4e0b48eef
8import Debug from 0xe81193c424cfd3fb
9import ViewResolver from 0x1d7e57aa55817448
10import Burner from 0xf233dcee88fe0abe
11import FlowtyViews from 0x3cdbb3d569211ff3
12
13access(all) contract Doodles: NonFungibleToken {
14
15	access(all) var totalSupply: UInt64
16
17	access(all) event ContractInitialized()
18	access(all) event Minted(id:UInt64, address:Address, set: String, setNumber: UInt64, name: String, context: {String : String})
19	access(all) event Equipped(id: UInt64, subId:UInt64, subSlot: String, resourceType: String, address:Address, tags: {String: String} , context: {String: String})
20	access(all) event Unequipped(id: UInt64, subId:UInt64, subSlot:String, resourceType: String, address:Address, tags: {String: String} , context: {String: String})
21	access(all) event SetRegistered(id: UInt64, name:String, royalties:[Templates.Royalty])
22	access(all) event SetRetired(id:UInt64, name:String)
23	access(all) event SpeciesRegistered(id: UInt64, name:String)
24	access(all) event SpeciesRetired(id: UInt64, name:String)
25 	access(all) event BaseCharacterRegistered(id: UInt64, species:String, name:String, baseCharacterTraits: {String: String}, image: String)
26	access(all) event BaseCharacterRetired(id: UInt64, name:String)
27	access(all) event DoodleUpdated(id: UInt64, owner: Address)
28
29	access(all) let CollectionStoragePath: StoragePath
30	access(all) let CollectionPublicPath: PublicPath
31
32	access(all) let sets: {UInt64: Set}
33	access(all) let species : {UInt64: Species}
34	access(all) let baseCharacters : {UInt64 : BaseCharacter}
35
36	access(all) struct Set : Templates.Retirable, Templates.Editionable, Templates.RoyaltyHolder {
37		access(all) let id:UInt64
38		access(all) let name: String
39		access(all) var active:Bool
40		access(all) let royalties: [Templates.Royalty]
41		access(self) let extra: {String : AnyStruct}
42
43		init(id:UInt64, name:String, royalties: [Templates.Royalty]) {
44			self.id=id
45			self.name=name
46			self.active=true
47			self.royalties=royalties
48			self.extra={}
49		}
50
51		access(all) fun getClassifier() : String{
52			return "set"
53		}
54
55		access(all) fun getCounterSuffix() : String {
56			return self.name
57		}
58
59		access(all) fun getContract() : String {
60			return "doodles"
61		}
62
63	}
64
65	access(all) struct Species : Templates.Retirable, Templates.Editionable {
66		access(all) let id: UInt64
67		access(all) let name: String
68		access(all) var active:Bool
69		access(self) let extra: {String : AnyStruct}
70
71		init(id: UInt64, name:String) {
72			self.id=id
73			self.name=name
74			self.active=true
75			self.extra={}
76		}
77
78		access(all) fun getClassifier() :String {
79			return "species"
80		}
81
82		access(all) fun getCounterSuffix() : String {
83			return self.name
84		}
85
86		access(all) fun getContract() : String {
87			return "doodles"
88		}
89	}
90
91	access(all) struct BaseCharacter : Templates.Retirable, Templates.Editionable{
92		access(all) let id: UInt64
93		access(all) let name: String
94		access(all) let species: UInt64
95		access(all) let set: UInt64
96		//TODO: What kind of traits exsist? Do we not want to have control over that?
97		access(all) let baseCharacterTraits: {String: BaseCharacterTrait}
98		access(all) var thumbnail: {MetadataViews.File}
99		access(all) let image: {MetadataViews.File}
100		access(all) var active:Bool
101		access(self) let extra: {String : AnyStruct}
102
103		init(id: UInt64, name:String, species:UInt64, set: UInt64, baseCharacterTraits: {String: BaseCharacterTrait}, thumbnail: {MetadataViews.File}, image: {MetadataViews.File}) {
104			self.id=id
105			self.species=species
106			self.name=name
107			self.set=set
108			self.baseCharacterTraits=baseCharacterTraits
109			self.thumbnail=thumbnail
110			self.image=image
111			self.active=true
112			self.extra={}
113		}
114
115		access(all) fun getClassifier() :String {
116			return "doodles"
117		}
118
119		access(all) fun getCounterSuffix() : String {
120			return self.name
121		}
122
123		access(all) fun getContract() : String {
124			return "doodles"
125		}
126
127		access(all) fun getSpecies() : Doodles.Species {
128			return Doodles.species[self.species]!
129		}
130
131		access(all) fun getSet() : Doodles.Set {
132			return Doodles.sets[self.set]!
133		}
134
135		access(all) fun getTraits() : [MetadataViews.Trait] {
136			let t : [MetadataViews.Trait] = []
137			for v in self.baseCharacterTraits.values {
138				if v.value != "" {
139					t.append(v.getTrait())
140				}
141			}
142			return t
143		}
144
145		access(all) fun getTraitsAsMap() : {String : String} {
146			let t : {String :  String} = {}
147			for key in self.baseCharacterTraits.keys {
148				t[key] = self.baseCharacterTraits[key]!.value
149			}
150			return t
151		}
152
153		access(account) fun updateTrait(name:String, value:String) {
154			if let curr = self.baseCharacterTraits[name] {
155				if curr.value!=value{
156					self.baseCharacterTraits[name]= BaseCharacterTrait(name:name, value:value)
157				}
158			} else {
159				self.baseCharacterTraits[name]= BaseCharacterTrait(name:name, value:value)
160			}
161		}
162
163	}
164
165	access(account) fun addSet(_ set: Doodles.Set) {
166		emit SetRegistered(id: set.id, name:set.name, royalties:set.royalties)
167		self.sets[set.id] = set
168	}
169
170	access(account) fun retireSet(_ id: UInt64) {
171		pre{
172			self.sets.containsKey(id) : "Set does not exist. Id : ".concat(id.toString())
173		}
174		emit SetRetired(id: id, name:self.sets[id]!.name)
175		self.sets[id]!.enable(false)
176	}
177
178	access(account) fun addSpecies(_ species: Doodles.Species) {
179		emit SpeciesRegistered(id: species.id, name: species.name)
180		self.species[species.id] = species
181	}
182
183	access(account) fun retireSpecies(_ id: UInt64) {
184		pre{
185			self.species.containsKey(id) : "Species does not exist. Id : ".concat(id.toString())
186		}
187		emit SpeciesRetired(id: id, name:self.species[id]!.name)
188		self.species[id]!.enable(false)
189	}
190
191	access(account) fun setBaseCharacter(_ bc: Doodles.BaseCharacter) {
192		// we do not check here because baseCharacterTraits are updatable and it can be overwritten
193		emit BaseCharacterRegistered(id: bc.id, species:bc.getSpecies().name, name:bc.name, baseCharacterTraits: bc.getTraitsAsMap(), image: bc.image.uri())
194		self.baseCharacters[bc.id] = bc
195	}
196
197	access(account) fun retireBaseCharacter(_ id: UInt64) {
198		pre{
199			self.baseCharacters.containsKey(id) : "Base Character does not exist. Id : ".concat(id.toString())
200		}
201		emit BaseCharacterRetired(id: id, name:self.baseCharacters[id]!.name)
202		self.baseCharacters[id]!.enable(false)
203	}
204
205	access(all) struct BaseCharacterTrait {
206		access(all) let name: String
207		access(all) let value: String
208		access(all) let tag: {String : String}
209		access(all) let extra: {String : AnyStruct}
210
211		init(name: String , value: String) {
212			self.name = name
213			self.value = value
214			self.tag = {}
215			self.extra = {}
216		}
217
218		access(all) fun getTrait() : MetadataViews.Trait {
219			return MetadataViews.Trait(name: "trait_".concat(self.name), value: self.value, displayType: "string", rarity: nil)
220		}
221
222		access(all) fun getTraitAsMap() : {String : String} {
223			let t = self.tag
224			t["name"] = self.name
225			t["value"] = self.value
226			return t
227		}
228	}
229
230	access(all) resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver, Burner.Burnable, ViewResolver.ResolverCollection {
231
232		access(all) let id:UInt64
233		// At the moment this will only store one name. A map of {id : name resource}
234		access(all) var name: @{UInt64 : DoodleNames.NFT}
235		access(all) var nounce:UInt64
236		access(all) let editions: [Templates.EditionInfo]
237		access(all) let baseCharacter: BaseCharacter
238
239		//This is resourceId to index for the item
240		access(all) let positionIndex: {UInt64 : UInt64}
241		// mapping of wearable ids to wearables resources
242		access(all) let wearables: @{UInt64: Wearables.NFT}
243
244		access(all) let royalties: MetadataViews.Royalties
245		access(all) let extra: {String : AnyStruct}
246
247		access(all) let context: { String: String}
248		init(
249			baseCharacter: BaseCharacter,
250			editions: [Templates.EditionInfo],
251			context: {String:String}
252
253		) {
254			self.nounce=0
255			self.id=self.uuid
256			self.name<-{}
257			self.editions=editions
258			self.baseCharacter=baseCharacter
259			self.wearables <- {}
260			self.positionIndex={}
261			let s = baseCharacter.getSet()
262			self.royalties=MetadataViews.Royalties(s.getRoyalties())
263			self.context=context
264			self.extra ={}
265		}
266
267		/// implemente ResolverCollection
268		access(all) view fun getIDs() : [UInt64] {
269			var keys=self.wearables.keys
270			keys = keys.concat(self.name.keys)
271			return keys
272		}
273
274		access(all) fun getSet() : Set {
275			return self.baseCharacter.getSet()
276		}
277
278		access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver} {
279			if self.name.containsKey(id) {
280				return (&self.name[id] as &DoodleNames.NFT?)!
281			}
282			return (&self.wearables[id] as &Wearables.NFT?)!
283		}
284
285		access(all) fun getContext() : {String:String} {
286			return self.context
287		}
288
289		access(contract) fun burnCallback() {
290			// Cannot move nested resources?
291			// destroy self.wearables
292			// destroy self.name
293		}
294
295		access(all) view fun getViews(): [Type] {
296			return  [
297			Type<MetadataViews.Display>(),
298			Type<MetadataViews.Medias>(),
299			Type<MetadataViews.Royalties>(),
300			Type<MetadataViews.ExternalURL>(),
301			Type<MetadataViews.NFTCollectionData>(),
302			Type<MetadataViews.NFTCollectionDisplay>(),
303			Type<MetadataViews.Editions>(),
304			Type<MetadataViews.Traits>(),
305			Type<FlowtyViews.DNA>()
306			]
307		}
308
309
310		access(all) fun resolveView(_ view: Type): AnyStruct? {
311
312			var fullMediaType="image/png"
313			let description="This Doodle is a uniquely personalized customizable character in a one-of-a-kind style."
314
315			let imageFile = self.baseCharacter.thumbnail
316
317			let fullMedia=MetadataViews.Media(file:self.baseCharacter.image, mediaType: fullMediaType)
318			let set = self.getSet()
319			
320			var name =""
321			if let ownedName = self.getName() {
322				name = ownedName
323			}
324
325			switch view {
326				case Type<MetadataViews.Display>():
327					return MetadataViews.Display(
328						name: name,
329						description: description,
330						thumbnail: imageFile
331					)
332				case Type<MetadataViews.ExternalURL>():
333					return MetadataViews.ExternalURL("https://doodles.app")
334				case Type<MetadataViews.Royalties>():
335					return self.royalties
336				case Type<MetadataViews.Medias>():
337					let medias: [MetadataViews.Media] = []
338					for key in self.wearables.keys{
339						let w = self.borrowWearable(key)
340						medias.append(w.getTemplate().thumbnail)
341					}
342					return MetadataViews.Medias(medias)
343				case Type<MetadataViews.Traits>():
344					return self.getTraitsAsTraits()
345				case Type<FlowtyViews.DNA>():
346					return FlowtyViews.DNA(self.calculateDNA())
347				case Type<MetadataViews.Editions>():
348					let edition=self.editions[0]
349					let active = self.getActive(edition.name)
350					let editions : [MetadataViews.Edition] =[
351						edition.getAsMetadataEdition(active)
352					]
353					return MetadataViews.Editions(editions)
354			}
355			return Doodles.resolveContractView(resourceType: Type<@Doodles.NFT>(), viewType: view)
356		}
357
358		access(all) fun getSetActive() : Bool {
359			let t = Doodles.sets[self.baseCharacter.set]!
360			return t.active
361		}
362
363		access(all) fun getSpeciesActive() : Bool {
364			let t = Doodles.species[self.baseCharacter.species]!
365			return t.active
366		}
367
368		access(all) fun getBaseCharacterActive() : Bool {
369			let t = Doodles.baseCharacters[self.baseCharacter.id]!
370			return t.active
371		}
372
373		access(all) fun getActive(_ classifier: String) : Bool {
374			switch classifier {
375				case "baseCharacter" :
376					return self.getBaseCharacterActive()
377
378				case "species" :
379					return self.getSpeciesActive()
380
381				case "set" :
382					return self.getSetActive()
383			}
384			return true
385		}
386
387		access(all) fun getName() : String? {
388			if let id = self.getNameId() {
389				let ref = self.borrowName(id)
390				return ref.name
391			}
392			return nil
393		}
394
395		access(all) fun getNameId() : UInt64? {
396			if self.name.length > 0 {
397				return self.name.keys[0]
398			}
399			return nil
400		}
401
402		access(all) fun getRoyalties() : [MetadataViews.Royalty] {
403			return self.royalties.getRoyalties()
404		}
405
406		//This needs to change
407		access(all) fun getWearablesAt(_ position: UInt64) : [UInt64] {
408			//map from index to resourceId
409			let ids : { UInt64 : UInt64}  = {}
410
411			for key in self.wearables.keys {
412				let w = self.borrowWearable(key)
413				if w.template.position == position {
414					ids[self.positionIndex[key]!]=key
415				}
416			}
417
418			var i=0
419			let idArrays : [UInt64]=[]
420			while(i < ids.length) {
421				idArrays.append(ids[UInt64(i)]!)
422				i=i+1
423			}
424			return idArrays
425		}
426
427		//todo: collapse
428		access(NonFungibleToken.Update) fun updateDoodle(wearableCollection: auth(NonFungibleToken.Withdraw) &Wearables.Collection, equipped: [UInt64],quote: String, expression: String, mood: String, background: String, hairStyle: String, hairColor: String, facialHair: String, facialHairColor: String, skinTone: String, pose: String, stage: String, location: String) {
429
430			let existingIds = self.wearables.keys
431
432			for wId in existingIds {
433				if equipped.contains(wId) {
434					//if an id is already equipped skip it
435					continue
436				}
437				let nft  <- self.wearables.remove(key: wId)!
438				let p = nft.template.getPosition()
439				emit Unequipped(id:self.id, subId: wId, subSlot: p.name, resourceType: nft.getType().identifier, address:self.owner!.address, tags: {"wearableId": wId.toString()}, context: {"wearablePosition" : p.name, "wearableName" : nft.template.name})
440				wearableCollection.deposit(token: <- nft)
441			}
442
443			for wId in equipped {
444				if existingIds.contains(wId) {
445					//we already have it on
446					continue
447				}
448
449				let wearableNFT <- wearableCollection.withdraw(withdrawID: wId)
450				let nft <- wearableNFT as! @Wearables.NFT
451				nft.equipped(owner: self.owner!.address, characterId: self.id)
452				let p = nft.template.getPosition()
453				let t = nft.getType()
454				emit Equipped(id:self.id, subId:wId, subSlot: p.name, resourceType: t.identifier, address:self.owner!.address, tags: {"wearableId": wId.toString()}, context: {"wearablePosition" : p.name, "wearableName" : nft.getName()})
455				self.wearables[wId] <-! nft
456
457			}
458
459			self.baseCharacter.updateTrait(name:"quote",value: quote)
460			self.baseCharacter.updateTrait(name:"expression",value: expression)
461			self.baseCharacter.updateTrait(name:"mood",value: mood)
462			self.baseCharacter.updateTrait(name:"background",value: background)
463			self.baseCharacter.updateTrait(name:"hair_style",value: hairStyle)
464			self.baseCharacter.updateTrait(name:"hair_color",value: hairColor)
465			self.baseCharacter.updateTrait(name:"facial_hair",value: facialHair)
466			self.baseCharacter.updateTrait(name:"facial_hair_color",value: facialHairColor)
467			self.baseCharacter.updateTrait(name:"skin_tone",value: skinTone)
468			self.baseCharacter.updateTrait(name:"pose",value: pose)
469			self.baseCharacter.updateTrait(name:"stage",value: stage)
470			self.baseCharacter.updateTrait(name:"location",value: location)
471
472			emit DoodleUpdated(id: self.id, owner: self.owner!.address)
473			Doodles.emitNFTUpdated(&self as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT})
474		}
475
476		//expand
477        access(NonFungibleToken.Update) fun editDoodle(wearableCollection: auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection, NonFungibleToken.Provider}, equipped: [UInt64],quote: String, expression: String, mood: String, background: String, hairStyle: String, hairColor: String, facialHair: String, facialHairColor: String, skinTone: String, pose: String, stage: String, location: String, hairPinched:Bool, hideExpression:Bool) {
478			self.internalEditDoodle(
479				wearableReceiver: wearableCollection,
480				wearableProviders: [wearableCollection],
481				equipped: equipped,
482				quote: quote,
483				expression: expression,
484				mood: mood,
485				background: background,
486				hairStyle: hairStyle,
487				hairColor: hairColor,
488				facialHair: facialHair,
489				facialHairColor: facialHairColor,
490				skinTone: skinTone,
491				pose: pose,
492				stage: stage,
493				location: location,
494				hairPinched: hairPinched,
495				hideExpression: hideExpression
496			)
497        }
498
499		access(NonFungibleToken.Update) fun editDoodleWithMultipleCollections(receiverWearableCollection: &{NonFungibleToken.Receiver}, wearableCollections: [auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection, NonFungibleToken.Provider}], equipped: [UInt64],quote: String, expression: String, mood: String, background: String, hairStyle: String, hairColor: String, facialHair: String, facialHairColor: String, skinTone: String, pose: String, stage: String, location: String, hairPinched:Bool, hideExpression:Bool) {
500			self.internalEditDoodle(
501				wearableReceiver: receiverWearableCollection,
502				wearableProviders: wearableCollections,
503				equipped: equipped,
504				quote: quote,
505				expression: expression,
506				mood: mood,
507				background: background,
508				hairStyle: hairStyle,
509				hairColor: hairColor,
510				facialHair: facialHair,
511				facialHairColor: facialHairColor,
512				skinTone: skinTone,
513				pose: pose,
514				stage: stage,
515				location: location,
516				hairPinched: hairPinched,
517				hideExpression: hideExpression
518			)
519		}
520
521		access(contract) fun internalEditDoodle(wearableReceiver: &{NonFungibleToken.Receiver}, wearableProviders: [auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection, NonFungibleToken.Provider}], equipped: [UInt64],quote: String, expression: String, mood: String, background: String, hairStyle: String, hairColor: String, facialHair: String, facialHairColor: String, skinTone: String, pose: String, stage: String, location: String, hairPinched:Bool, hideExpression:Bool) {
522			let existingIds = self.wearables.keys
523
524            for wId in existingIds {
525                if equipped.contains(wId) {
526                    //if an id is already equipped skip it
527                    continue
528                }
529                let nft  <- self.wearables.remove(key: wId)!
530                let p = nft.template.getPosition()
531                emit Unequipped(id:self.id, subId: wId, subSlot: p.name, resourceType: nft.getType().identifier, address:self.owner!.address, tags: {"wearableId": wId.toString()}, context: {"wearablePosition" : p.name, "wearableName" : nft.template.name})
532                wearableReceiver.deposit(token: <- nft)
533            }
534
535			for wearableProvider in wearableProviders {
536				for wId in equipped {
537					if !wearableProvider.getIDs().contains(wId) {
538						continue
539					}
540
541					let wearableNFT <- wearableProvider.withdraw(withdrawID: wId)
542					let nft <- wearableNFT as! @Wearables.NFT
543					nft.equipped(owner: self.owner!.address, characterId: self.id)
544					let p = nft.template.getPosition()
545					let t = nft.getType()
546					emit Equipped(id:self.id, subId:wId, subSlot: p.name, resourceType: t.identifier, address:self.owner!.address, tags: {"wearableId": wId.toString()}, context: {"wearablePosition" : p.name, "wearableName" : nft.getName()})
547					self.wearables[wId] <-! nft
548
549				}
550			}
551
552            var hairPinchedValue=""
553            if hairPinched {
554                hairPinchedValue="true"
555            }
556
557            var hideExpressionValue=""
558            if hideExpression {
559                hideExpressionValue="true"
560            }
561
562            self.baseCharacter.updateTrait(name:"quote",value: quote)
563            self.baseCharacter.updateTrait(name:"expression",value: expression)
564            self.baseCharacter.updateTrait(name:"expression_hide",value: hideExpressionValue)
565            self.baseCharacter.updateTrait(name:"mood",value: mood)
566            self.baseCharacter.updateTrait(name:"background",value: background)
567            self.baseCharacter.updateTrait(name:"hair_style",value: hairStyle)
568            self.baseCharacter.updateTrait(name:"hair_color",value: hairColor)
569            self.baseCharacter.updateTrait(name:"hair_pinched",value: hairPinchedValue)
570            self.baseCharacter.updateTrait(name:"facial_hair",value: facialHair)
571            self.baseCharacter.updateTrait(name:"facial_hair_color",value: facialHairColor)
572            self.baseCharacter.updateTrait(name:"skin_tone",value: skinTone)
573            self.baseCharacter.updateTrait(name:"pose",value: pose)
574            self.baseCharacter.updateTrait(name:"stage",value: stage)
575            self.baseCharacter.updateTrait(name:"location",value: location)
576
577			emit DoodleUpdated(id: self.id, owner: self.owner!.address)
578			Doodles.emitNFTUpdated(&self as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT})
579		}
580
581		access(account) fun addName(_ nft: @DoodleNames.NFT, owner:Address) {
582			pre{
583				self.name.length == 0 : "This doodles have name equipped ID : ".concat(self.name.keys[0].toString())
584			}
585
586			nft.deposited(owner: owner, characterId: self.id)
587			self.name[nft.id] <-! nft
588		}
589
590		access(NonFungibleToken.Update) fun equipName(_ nft: @DoodleNames.NFT) {
591			pre{
592				self.name.length == 0 : "This doodles have name equipped ID : ".concat(self.name.keys[0].toString())
593			}
594
595			let action = "equipName"
596			Templates.assertFeatureEnabled(action)
597
598			let resourceId = nft.id
599			let t = nft.getType()
600			let characterName = nft.name
601			nft.deposited(owner: self.owner!.address, characterId: self.id)
602			self.name[nft.id] <-! nft
603			emit Equipped(id:self.id, subId: resourceId, subSlot: "name", resourceType: t.identifier, address:self.owner!.address, tags: {}, context: {"Name" : characterName})
604		}
605
606		access(contract) fun unequipName() : @DoodleNames.NFT {
607			pre{
608				self.name.length > 0 : "This character does not have name equipped ID : ".concat(self.id.toString())
609			}
610
611			let action = "unequipName"
612			Templates.assertFeatureEnabled(action)
613
614			let resourceId = self.getNameId()!
615			let nft <- self.name.remove(key: resourceId)!
616			nft.withdrawn()
617			let characterName = nft.name
618			let t = nft.getType()
619			emit Unequipped(id:self.id, subId: resourceId, subSlot: "name", resourceType: t.identifier, address:self.owner!.address, tags: {}, context: {"Name" : characterName})
620			return <- nft
621		}
622
623		access(contract) fun equipWearable(_ nft: @Wearables.NFT, index: UInt64) {
624			let action = "equipWearable"
625			Templates.assertFeatureEnabled(action)
626
627			let resourceId = nft.id
628			let t = nft.getType()
629			let name = nft.template.name
630			let p = nft.template.getPosition()
631			let positionCount = p.getPositionCount()
632			if UInt64(positionCount - 1) < index {
633				panic("Position index does not exist. Maximum index : ".concat((positionCount - 1).toString()))
634			}
635			let equipped = self.getWearablesAt(p.id)
636
637			//loop over these and order them by the value in positionIndex
638
639			for w in equipped {
640				if self.positionIndex[w]! == index {
641					panic("Position with index is already equipped. index : ".concat(index.toString()) )
642				}
643			}
644			self.positionIndex[resourceId] = index
645			equipped.append(resourceId)
646			assert(equipped.length <= positionCount, message : "You already equipped more wearables than you can at this position : ".concat(p.name).concat(" Max number of wearables : ".concat(positionCount.toString())))
647			nft.equipped(owner: self.owner!.address, characterId: self.id)
648			self.wearables[resourceId] <-! nft
649			assert(equipped.length <= positionCount, message : "You already equipped more wearables than you can at this position : ".concat(p.name).concat(" Max number of wearables : ".concat(positionCount.toString())))
650			emit Equipped(id:self.id, subId: resourceId, subSlot: p.name, resourceType: t.identifier, address:self.owner!.address, tags: {"wearableId": resourceId.toString()}, context: {"wearablePosition" : p.name, "wearableName" : name})
651		}
652
653		access(contract) fun unequipWearable(_ resourceId: UInt64) : @Wearables.NFT {
654			pre{
655				self.wearables.containsKey(resourceId) : "Wearables with id is not equipped : ".concat(resourceId.toString())
656			}
657
658			self.positionIndex.remove(key: resourceId)!
659			let action = "unequipWearable"
660			Templates.assertFeatureEnabled(action)
661
662
663			let nft  <- self.wearables.remove(key: resourceId)!
664			let p = nft.template.getPosition()
665			emit Unequipped(id:self.id, subId: resourceId, subSlot: p.name, resourceType: nft.getType().identifier, address:self.owner!.address, tags: {"wearableId": resourceId.toString()}, context: {"wearablePosition" : p.name, "wearableName" : nft.template.name})
666			return <- nft
667		}
668
669		access(all) fun borrowWearable(_ id: UInt64) : &Wearables.NFT {
670			return (&self.wearables[id])!
671		}
672
673		access(all) fun borrowName(_ id: UInt64) : &DoodleNames.NFT {
674			return (&self.name[id])!
675		}
676
677		access(all) fun borrowWearableViewResolver(_ id: UInt64) : &{ViewResolver.Resolver}? {
678			if let nft = &self.wearables[id] as &Wearables.NFT? {
679                return nft
680            }
681            return nil
682		}
683
684		access(contract) fun increaseNounce() {
685			self.nounce=self.nounce+1
686		}
687
688		access(all) fun getTraitsAsTraits() : MetadataViews.Traits {
689			let traits=self.getAllTraitsMetadata()
690			let res : [MetadataViews.Trait] = []
691			for t in traits.values {
692				res.appendAll(t)
693			}
694			return MetadataViews.Traits(res)
695		}
696
697		access(all) fun getAllTraitsMetadata() : {String : [MetadataViews.Trait]} {
698			var traitMetadata : [MetadataViews.Trait] = self.baseCharacter.getTraits()
699			let wearableTraits = self.getWearableTraits()
700			wearableTraits["doodles"] = traitMetadata
701			if let nameTrait = self.getNameTrait() {
702				wearableTraits["name"] = [nameTrait]
703			}
704
705      		wearableTraits["dna"] = [MetadataViews.Trait(name:"dna", value:self.calculateDNA(), displayType:"string", rarity:nil)]
706
707			let ctx = self.getContext()
708			for key in ctx.keys{
709				let traitKey ="context_".concat(key)
710				wearableTraits[traitKey]=[MetadataViews.Trait(name:traitKey, value: ctx[key], displayType:"string", rarity:nil)]
711			}
712			return wearableTraits
713		}
714
715		access(all) fun getNameTrait() : MetadataViews.Trait? {
716			if let name = self.getName() {
717				return MetadataViews.Trait(name: "doodle_name", value: name, displayType:"string", rarity:nil)
718			}
719			return nil
720		}
721
722    	//b64(<doodleName>-<pose value>-<expression value>-<skinTone value>-<hairStyle value>-<hairColor value>-<id of doodle>-[<array of equipped NFT Ids>])
723		access(all) fun calculateDNA() : String {
724			return self.calculateDNACustom(sep: "|", encode: true)
725		}
726
727		access(all) fun calculateDNACustom(sep: String, encode: Bool) : String {
728			let seperator=sep
729			let traits =self.baseCharacter.getTraitsAsMap()
730			var dna=""
731			dna=dna.concat(self.getName() ?? "").concat(seperator)
732			dna=dna.concat(traits["pose"] ?? "").concat(seperator)
733			dna=dna.concat(traits["expression"]?? "").concat(seperator)
734			dna=dna.concat(traits["skin_tone"] ?? "").concat(seperator)
735			dna=dna.concat(traits["hair_style"]?? "").concat(seperator)
736			dna=dna.concat(traits["hair_color"] ?? "").concat(seperator)
737			dna=dna.concat(self.id.toString()).concat(seperator)
738			dna=dna.concat("[")
739
740			//https://www.geeksforgeeks.org/insertion-sort/
741			var ids = self.wearables.keys
742					for i, key in ids {
743				var j=i
744				while j >0 && ids[j-1] > ids[j] {
745				ids[j] <-> ids[j-1]
746				j=j-1
747				}
748			}
749
750			for id in ids {
751				dna = dna.concat(id.toString()).concat(seperator)
752			}
753			//remove the last seperator
754			dna=dna.slice(from: 0, upTo: dna.length - seperator.length)
755			dna=dna.concat("]")
756
757			if !encode {
758				return dna
759			}
760			return Doodles.base64encode(dna)
761		}
762
763		access(all) fun getWearableTraits() : {String : [MetadataViews.Trait]} {
764			let traitsToKeep = ["name",  "position", "set", "layer"]
765			let mvt : {String : [MetadataViews.Trait]} = {}
766			let counter : {UInt64:Int} = {}
767			for key in self.wearables.keys{
768				let w = self.borrowWearable(key)
769
770				let position=w.getTemplate().id.toString()
771
772				let trait = MetadataViews.getTraits(w)
773				if trait != nil {
774					let cleanedTraits : [MetadataViews.Trait] = []
775					let traitIdentifer = "wearable_".concat(position).concat("_")
776
777					let idTrait=MetadataViews.Trait(name: traitIdentifer.concat("id"), value: w.id, displayType:"Number", rarity:nil)
778					cleanedTraits.append(idTrait)
779
780					let templateIdTrait=MetadataViews.Trait(name: traitIdentifer.concat("template_id"), value: w.getTemplate().id, displayType:"Number", rarity:nil)
781					cleanedTraits.append(templateIdTrait)
782
783					for t in trait!.traits {
784						if !traitsToKeep.contains(t.name) {
785							continue
786						}
787						let traitName = FindUtils.trimSuffix(traitIdentifer.concat(t.name), suffix: "_name")
788						let newTrait=MetadataViews.Trait(name: traitName, value: t.value, displayType:t.displayType, rarity:t.rarity)
789						cleanedTraits.append(newTrait)
790					}
791
792					let array = mvt[position] ?? []
793					array.appendAll(cleanedTraits)
794					mvt[position] = array
795				}
796			}
797			return mvt
798		}
799
800		/// Gets all the NFTs that this NFT directly owns
801        /// @return A dictionary of all subNFTS keyed by type
802        access(all) view fun getAvailableSubNFTS(): {Type: [UInt64]} {
803			let ownedNFTs: {Type: [UInt64]} = {}
804			if self.wearables.length > 0 {
805				let t = Type<@Wearables.NFT>()
806				var ids : [UInt64] = []
807				for key in self.wearables.keys {
808					ids = ids.concat([key])
809				}
810				ownedNFTs[t] = ids
811			}
812			if self.name.length > 0 {
813				let t = Type<@DoodleNames.NFT>()
814				ownedNFTs[t] = [self.name.keys[0]]
815			}	
816            return ownedNFTs
817        }
818
819        /// Get a reference to an NFT that this NFT owns
820        /// Both arguments are optional to allow the NFT to choose
821        /// how it returns sub NFTs depending on what arguments are provided
822        /// For example, if `type` has a value, but `id` doesn't, the NFT 
823        /// can choose which NFT of that type to return if there is a "default"
824        /// If both are `nil`, then NFTs that only store a single NFT can just return
825        /// that. This helps callers who aren't sure what they are looking for 
826        ///
827        /// @param type: The Type of the desired NFT
828        /// @param id: The id of the NFT to borrow
829        ///
830        /// @return A structure representing the requested view.
831        access(all) fun getSubNFT(type: Type, id: UInt64) : &{NonFungibleToken.NFT}? {
832            switch type {
833				case Type<@Wearables.NFT>():
834					return &self.wearables[id]
835				case Type<@DoodleNames.NFT>():
836					return &self.name[id]
837				default:
838					return nil
839			}
840        }
841
842		access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
843			return <- Doodles.createEmptyCollection(nftType: Type<@Doodles.NFT>())
844		}
845	}
846
847	access(all) resource interface DoodlesCollectionPublic {
848        access(all) fun deposit(token: @{NonFungibleToken.NFT})
849        access(all) view fun getIDs(): [UInt64]
850        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
851        access(all) view fun borrowDoodlesNFT(id: UInt64) : &NFT?
852		access(NonFungibleToken.Update) fun borrowDoodlesNFTUpdate(id: UInt64): auth(NonFungibleToken.Update) &NFT?
853    }
854
855	access(all) resource Collection: DoodlesCollectionPublic, NonFungibleToken.Collection {
856		// dictionary of NFT conforming tokens
857		// NFT is a resource type with an `UInt64` ID field
858		access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
859
860		init () {
861			self.ownedNFTs <- {}
862		}
863
864		// withdraw removes an NFT from the collection and moves it to the caller
865		access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
866			let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
867			return <-token
868		}
869
870		// deposit takes a NFT and adds it to the collections dictionary
871		// and adds the ID to the id array
872		access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
873			let token <- token as! @NFT
874
875			let id: UInt64 = token.id
876
877			token.increaseNounce()
878			// add the new token to the dictionary which removes the old one
879			let oldToken <- self.ownedNFTs[id] <- token
880
881
882			destroy oldToken
883		}
884
885		// getIDs returns an array of the IDs that are in the collection
886		access(all) view fun getIDs(): [UInt64] {
887			return self.ownedNFTs.keys
888		}
889
890		// borrowNFT gets a reference to an NFT in the collection
891		// so that the caller can read its metadata and call its methods
892		access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
893			return &self.ownedNFTs[id]
894		}
895
896		access(all) view fun borrowDoodlesNFT(id: UInt64) : &Doodles.NFT? {
897			return &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? as! &Doodles.NFT?
898		}
899
900		access(NonFungibleToken.Update) fun borrowDoodlesNFTUpdate(id: UInt64) : auth(NonFungibleToken.Update) &Doodles.NFT? {
901			return &self.ownedNFTs[id] as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT}? as! auth(NonFungibleToken.Update) &Doodles.NFT?
902		}
903
904		access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
905			if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? as! &Doodles.NFT? {
906                return nft
907            }
908            return nil
909		}
910
911		access(all) view fun getLength(): Int {
912            return self.ownedNFTs.keys.length
913        }
914
915        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
916            let supportedTypes: {Type: Bool} = {}
917            supportedTypes[Type<@Doodles.NFT>()] = true
918            return supportedTypes
919        }
920
921        access(all) view fun isSupportedNFTType(type: Type): Bool {
922           if type == Type<@Doodles.NFT>() {
923        	return true
924           } else {
925        	return false
926           }
927		}
928
929		access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
930			return <- Doodles.createEmptyCollection(nftType: Type<@Doodles.NFT>())
931		}
932	}
933
934	// public function that anyone can call to create a new empty collection
935	access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
936		return <- create Collection()
937	}
938
939	access(account) fun mintDoodle(
940		recipient: &{NonFungibleToken.Receiver},
941		betaPass: @Wearables.NFT,
942		doodleName: String,
943		baseCharacter: UInt64
944	){
945		pre {
946			recipient.owner != nil : "Recipients NFT collection is not owned"
947		}
948
949		let template: Wearables.Template = betaPass.getTemplate()
950		assert(template.id == 244, message: "Not a valid beta pass")
951		let context: {String: String} = betaPass.context
952		destroy <- betaPass
953
954		let newNFT <- self.adminMintDoodle(
955			recipientAddress: recipient.owner!.address,
956			doodleName: doodleName,
957			baseCharacter: baseCharacter,
958			context: context
959		)
960
961		recipient.deposit(token: <-newNFT)
962	}
963
964	// mintNFT mints a new NFT with a new ID
965	// and deposit it in the recipients collection using their collection reference
966	//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
967	//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
968	access(account) fun adminMintDoodle(
969		recipientAddress: Address,
970		doodleName: String,
971		baseCharacter: UInt64,
972		context: {String : String}
973	): @Doodles.NFT {
974		pre {
975			self.baseCharacters.containsKey(baseCharacter) : "Base Character does not exist. Id : ".concat(baseCharacter.toString())
976		}
977
978		Doodles.totalSupply = Doodles.totalSupply + 1
979		let baseCharacter = Doodles.borrowBaseCharacter(baseCharacter)
980		let set = Doodles.borrowSet(baseCharacter.set)
981		let species =  Doodles.borrowSpecies(baseCharacter.species)
982
983		assert(set.active, message: "Set Retired : ".concat(set.name))
984		assert(species.active, message: "Species Retired : ".concat(species.name))
985		assert(baseCharacter.active, message: "BaseCharacter Retired : ".concat(baseCharacter.name))
986
987		let name <- DoodleNames.mintName(name:doodleName, context:context, address: recipientAddress)
988
989		let editions=[
990			baseCharacter.createEditionInfo(nil),
991			species.createEditionInfo(nil),
992			set.createEditionInfo(nil)
993		]
994
995		// create a new NFT
996		var newNFT <- create NFT(
997			baseCharacter: Doodles.baseCharacters[baseCharacter.id]!,
998			editions: editions,
999			context:context
1000		)
1001		newNFT.addName(<- name, owner: recipientAddress)
1002
1003		emit Minted(id:newNFT.id, address: recipientAddress, set: set.name, setNumber: set.id, name: doodleName, context: context)
1004
1005		return <- newNFT
1006	}
1007
1008	access(account) fun borrowSet(_ id: UInt64) : &Doodles.Set {
1009		pre{
1010			self.sets.containsKey(id) : "Set does not exist. Id : ".concat(id.toString())
1011		}
1012		return &Doodles.sets[id]!
1013	}
1014
1015	access(account) fun borrowSpecies(_ id: UInt64) : &Doodles.Species {
1016		pre{
1017			self.species.containsKey(id) : "Species does not exist. Id : ".concat(id.toString())
1018		}
1019		return &Doodles.species[id]!
1020	}
1021
1022	access(account) fun borrowBaseCharacter(_ id: UInt64) : &Doodles.BaseCharacter {
1023		pre{
1024			self.baseCharacters.containsKey(id) : "BaseCharacter does not exist. Id : ".concat(id.toString())
1025		}
1026		return &Doodles.baseCharacters[id]!
1027	}
1028
1029	access(all) view fun getContractViews(resourceType: Type?): [Type] {
1030        return [
1031            Type<MetadataViews.NFTCollectionDisplay>(),
1032            Type<MetadataViews.NFTCollectionData>()
1033        ]
1034    }
1035
1036    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
1037		let description="This Doodle is a uniquely personalized customizable character in a one-of-a-kind style."
1038        switch viewType {
1039            case Type<MetadataViews.NFTCollectionDisplay>():
1040				let externalURL = MetadataViews.ExternalURL("https://doodles.app")
1041				let squareImage = MetadataViews.Media(file: MetadataViews.IPFSFile(cid: "QmVpAiutpnzp3zR4q2cUedMxsZd8h5HDeyxs9x3HibsnJb", path:nil), mediaType:"image/png")
1042				let bannerImage = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://res.cloudinary.com/hxn7xk7oa/image/upload/v1675121458/doodles2_banner_ee7a035d05.jpg"), mediaType: "image/jpeg")
1043				return MetadataViews.NFTCollectionDisplay(
1044					name: "Doodles",
1045					description: description, 
1046					externalURL: externalURL, 
1047					squareImage: squareImage, 
1048					bannerImage: bannerImage, 
1049					socials: { 
1050						"discord": MetadataViews.ExternalURL("https://discord.gg/doodles"), 
1051						"twitter" : MetadataViews.ExternalURL("https://twitter.com/Doodles")
1052						}
1053				)
1054			case Type<MetadataViews.NFTCollectionData>():
1055				return MetadataViews.NFTCollectionData(
1056					storagePath: Doodles.CollectionStoragePath,
1057					publicPath: Doodles.CollectionPublicPath,
1058					publicCollection: Type<&Collection>(),
1059					publicLinkedType: Type<&Collection>(),
1060					createEmptyCollectionFunction: (fun(): 
1061						@{NonFungibleToken.Collection} {return <- Doodles.createEmptyCollection(nftType: Type<@Doodles.NFT>())})
1062					)
1063        }
1064        return nil
1065    }
1066
1067	//MOVE TO UTIL
1068	//https://forum.onflow.org/t/base64-encode-in-cadence/1915/6
1069  	access(all) fun base64encode(_ input: String): String {
1070		let data = input.utf8
1071
1072		let baseChars: [String] = [
1073		"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
1074		"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
1075		"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"
1076		]
1077
1078		var encoded = ""
1079		var padding = ""
1080		var padCount = data.length % 3
1081
1082		// Add a right zero pad to make the input a multiple of 3 characters
1083		if padCount > 0 {
1084			while padCount < 3 {
1085				padding = padding.concat("=")
1086				data.append(0)
1087				padCount = padCount + 1
1088			}
1089		}
1090
1091		// Increment over the length of the input, three bytes at a time
1092		var i = 0
1093		while i < data.length {
1094			// Each three bytes become one 24-bit number
1095			let n = (UInt32(data[i]) << 16)
1096				+ (UInt32(data[i + 1]) << 8)
1097				+ UInt32(data[i + 2])
1098
1099			// This 24-bit number gets separated into four 6-bit numbers
1100			let n1 = (n >> 18) & 63
1101			let n2 = (n >> 12) & 63
1102			let n3 = (n >> 6) & 63
1103			let n4 = n & 63
1104
1105			// Those four 6-bit numbers are used as indices into the base64 character list
1106			encoded = encoded
1107				.concat(baseChars[n1])
1108				.concat(baseChars[n2])
1109				.concat(baseChars[n3])
1110				.concat(baseChars[n4])
1111
1112			i = i + 3
1113   		}
1114
1115		return encoded
1116			.slice(from: 0, upTo: encoded.length - padding.length)
1117			.concat(padding)
1118	}
1119
1120	init() {
1121		// Initialize the total supply
1122		self.totalSupply = 0
1123
1124		self.sets = {}
1125		self.species = {}
1126		self.baseCharacters = {}
1127
1128		// Set the named paths
1129		self.CollectionStoragePath = /storage/doodles
1130		self.CollectionPublicPath = /public/doodles
1131
1132		self.account.storage.save<@{NonFungibleToken.Collection}>(<- Doodles.createEmptyCollection(nftType: Type<@Doodles.NFT>()), to: Doodles.CollectionStoragePath)
1133		let cap = self.account .capabilities.storage.issue<&Collection>(Doodles.CollectionStoragePath)
1134		self.account.capabilities.publish(cap, at: Doodles.CollectionPublicPath)
1135
1136		emit ContractInitialized()
1137	}
1138}