Smart Contract
Wearables
A.e81193c424cfd3fb.Wearables
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}