Smart Contract

Seussibles

A.321d8fcde05f6e8c.Seussibles

Valid From

117,592,769

Deployed

2w ago
Feb 11, 2026, 06:34:56 PM UTC

Dependents

3451 imports
1/**/
2import MetadataViews from 0x1d7e57aa55817448
3import ViewResolver from 0x1d7e57aa55817448
4import NonFungibleToken from 0x1d7e57aa55817448
5import FungibleToken from 0xf233dcee88fe0abe
6import TiblesApp from 0x5cdeb067561defcb
7import TiblesNFT from 0x5cdeb067561defcb
8import TiblesProducer from 0x5cdeb067561defcb
9import CrossVMMetadataViews from 0x1d7e57aa55817448
10import EVM from 0xe467b9dd11fa00df
11
12access(all) contract Seussibles:
13  NonFungibleToken,
14  TiblesApp,
15  TiblesNFT,
16  TiblesProducer
17{
18  access(all) let appId: String
19  access(all) let title: String
20  access(all) let description: String
21  access(all) let ProducerStoragePath: StoragePath
22  access(all) let ProducerPath: PrivatePath
23  access(all) let ContentPath: PublicPath
24  access(all) let contentCapability: Capability
25  access(all) let CollectionStoragePath: StoragePath
26  access(all) let PublicCollectionPath: PublicPath
27
28  access(all) event ContractInitialized()
29  access(all) event Withdraw(id: UInt64, from: Address?)
30  access(all) event Deposit(id: UInt64, to: Address?)
31  access(all) event MinterCreated(minterId: String)
32  access(all) event TibleMinted(minterId: String, mintNumber: UInt32, id: UInt64)
33  access(all) event TibleDestroyed(id: UInt64)
34  access(all) event PackMinterCreated(minterId: String)
35  access(all) event PackMinted(id: UInt64, printedPackId: String)
36
37  access(all) var totalSupply: UInt64
38
39  access(all) resource NFT: NonFungibleToken.NFT, TiblesNFT.INFT {
40    access(all) let id: UInt64
41    access(all) let mintNumber: UInt32
42
43    access(self) let contentCapability: Capability
44    access(self) let contentId: String
45
46    init(id: UInt64, mintNumber: UInt32, contentCapability: Capability, contentId: String) {
47      self.id = id
48      self.mintNumber = mintNumber
49      self.contentId = contentId
50      self.contentCapability = contentCapability
51    }
52
53    access(all) fun metadata(): {String: AnyStruct}? {
54      let content = self.contentCapability.borrow<&{TiblesProducer.IContent}>() ?? panic("Failed to borrow content provider")
55      return content.getMetadata(contentId: self.contentId)
56    }
57
58    access(all) fun displayData(): {String: String} {
59      let metadata = self.metadata() ?? panic("Missing NFT metadata")
60
61      if (metadata.containsKey("pack")) {
62        return {
63          "name": "Seussibles pack",
64          "description": "A Seussibles pack",
65          "imageUrl": "https://app.seussibles.com/collection/"
66        }
67      }
68
69      let set = metadata["set"]! as! &Seussibles.Set
70      let item = metadata["item"]! as! &Seussibles.Item
71      let variant = metadata["variant"]! as! &Seussibles.Variant
72
73      var edition: String = ""
74      var serialInfo: String = ""
75      if let maxCount = variant.maxCount() {
76        edition = "Limited Edition"
77        serialInfo = "LE | "
78          .concat(variant.title())
79          .concat(" #")
80          .concat(self.mintNumber.toString())
81          .concat("/")
82          .concat(maxCount.toString())
83      } else if let batchSize = variant.batchSize() {
84        edition = "Standard Edition"
85        let mintSeries = (self.mintNumber - 1) / batchSize + 1
86        serialInfo = "S".concat(mintSeries.toString())
87          .concat(" | ")
88          .concat(variant.title())
89          .concat(" #")
90          .concat(self.mintNumber.toString())
91      } else {
92        panic("Missing batch size and max count")
93      }
94
95      let description = serialInfo
96        .concat("\n")
97        .concat(edition)
98        .concat("\n")
99        .concat(set.title())
100
101      let imageUrl = item.imageUrl(variantId: variant.id)
102
103      return {
104        "name": item.title(),
105        "description": description,
106        "imageUrl": imageUrl,
107        "edition": edition,
108        "serialInfo": serialInfo
109      }
110    }
111
112    access(all) fun display(): MetadataViews.Display {
113      let nftData = self.displayData()
114
115      return MetadataViews.Display(
116        name: nftData["name"] ?? "",
117        description: nftData["description"] ?? "",
118        thumbnail: MetadataViews.HTTPFile(url: nftData["imageUrl"] ?? "")
119      )
120    }
121
122    access(all) fun editions(): MetadataViews.Editions {
123      let nftData = self.displayData()
124      let metadata = self.metadata() ?? panic("Missing NFT metadata")
125      if (metadata.containsKey("pack")) {
126         return MetadataViews.Editions([MetadataViews.Edition(name: "Seussibles pack", number: UInt64(self.mintNumber), max: nil)])
127      }
128
129      let variant = metadata["variant"]! as! &Seussibles.Variant
130
131      var maxCount: UInt64? = nil
132      if let count = variant.maxCount() {
133        maxCount = UInt64(count)
134      }
135
136      let editionInfo = MetadataViews.Edition(
137        name: nftData["edition"] ?? "",
138        number: UInt64(self.mintNumber),
139        max: maxCount
140      )
141
142      let editionList: [MetadataViews.Edition] = [editionInfo]
143      return MetadataViews.Editions(editionList)
144    }
145
146    access(all) fun serial(): MetadataViews.Serial {
147      return MetadataViews.Serial(UInt64(self.mintNumber))
148    }
149
150    access(all) view fun royalties(): MetadataViews.Royalties {
151      let feeRecCap = getAccount(0x1f590411eaca135f)
152        .capabilities
153        .get<&{FungibleToken.Receiver}>(/public/dapperUtilityCoinReceiver)
154
155      return MetadataViews.Royalties([
156        MetadataViews.Royalty(
157          receiver: feeRecCap,
158          cut: UFix64(0.025), // 2.5% royalty
159          description: "Seussibles DUC Royalty",
160        )
161      ])
162    }
163
164    access(all) fun externalURL(): MetadataViews.ExternalURL {
165      return MetadataViews.ExternalURL("https://app.seussibles.com/collection/".concat(self.id.toString()))
166    }
167
168    access(all) fun nftCollectionData(): MetadataViews.NFTCollectionData {
169      return MetadataViews.NFTCollectionData(
170        storagePath: Seussibles.CollectionStoragePath,
171        publicPath: Seussibles.PublicCollectionPath,
172        publicCollection: Type<&Seussibles.Collection>(),
173        publicLinkedType: Type<&Seussibles.Collection>(),
174        createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
175          return <-Seussibles.createEmptyCollection(nftType: Type<@Seussibles.NFT>())
176        })
177      )
178    }
179
180    access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
181       return <-Seussibles.createEmptyCollection(nftType: Type<@Seussibles.NFT>())
182    }
183
184    access(all) fun nftCollectionDisplay(): MetadataViews.NFTCollectionDisplay {
185      let squareMedia = MetadataViews.Media(
186        file: MetadataViews.HTTPFile(url: "https://i.tibles.com/m/seussibles-flow-icon.png"),
187        mediaType: "image/svg+xml"
188      )
189      let bannerMedia = MetadataViews.Media(
190        file: MetadataViews.HTTPFile(url: "https://i.tibles.com/m/seussibles-flow-collection-banner.png"),
191        mediaType: "image/png"
192      )
193
194      let socialsData: {String: String} = {"twitter":"https://twitter.com/seussibles","discord":"https://discord.gg/tibles"}
195      let socials:{String: MetadataViews.ExternalURL } = {}
196      for key in socialsData.keys {
197        socials[key] = MetadataViews.ExternalURL(socialsData[key]!)
198      }
199
200      return MetadataViews.NFTCollectionDisplay(
201        name: "Seussibles! Dr. Seuss Collection by Tibles",
202        description: "An officially licensed Dr. Seuss digital collecting experience by Tibles. Available on iOS and web, Seussibles! is super accessible, engaging, and fun!",
203        externalURL: MetadataViews.ExternalURL("https://seussibles.com"),
204        squareImage: squareMedia,
205        bannerImage: bannerMedia,
206        socials: socials
207      )
208    }
209
210    access(all) view fun traits(): MetadataViews.Traits {
211      let traits : [MetadataViews.Trait] = []
212      return MetadataViews.Traits(traits)
213    }
214
215    access(all) view fun getViews(): [Type] {
216      return [
217        Type<MetadataViews.Display>(),
218        Type<MetadataViews.Royalties>(),
219        Type<MetadataViews.Editions>(),
220        Type<MetadataViews.ExternalURL>(),
221        Type<MetadataViews.NFTCollectionData>(),
222        Type<MetadataViews.NFTCollectionDisplay>(),
223        Type<MetadataViews.Serial>(),
224        Type<MetadataViews.Traits>(),
225        Type<CrossVMMetadataViews.EVMPointer>()
226      ]
227    }
228
229    access(all) fun resolveView(_ view: Type): AnyStruct? {
230      switch view {
231        case Type<MetadataViews.Display>():
232          return self.display()
233        case Type<MetadataViews.Editions>():
234          return self.editions()
235        case Type<MetadataViews.Serial>():
236          return self.serial()
237        case Type<MetadataViews.Royalties>():
238          return self.royalties()
239        case Type<MetadataViews.ExternalURL>():
240          return self.externalURL()
241        case Type<MetadataViews.NFTCollectionData>():
242          return self.nftCollectionData()
243        case Type<MetadataViews.NFTCollectionDisplay>():
244          return self.nftCollectionDisplay()
245        case Type<MetadataViews.Traits>():
246          return self.traits()
247        case Type<CrossVMMetadataViews.EVMPointer>():
248          return Seussibles.resolveContractView(resourceType: nil, viewType: Type<CrossVMMetadataViews.EVMPointer>())
249        default: return nil
250      }
251    }
252  }
253
254  access(all) resource Collection:
255    NonFungibleToken.Collection,
256    TiblesNFT.CollectionPublic
257  {
258    access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
259
260    access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
261      let supportedTypes: {Type: Bool} = {}
262      supportedTypes[Type<@Seussibles.NFT>()] = true
263      return supportedTypes
264    }
265
266    access(all) view fun isSupportedNFTType(type: Type): Bool {
267      return type == Type<@Seussibles.NFT>()
268    }
269
270    access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
271      let tible <- token as! @Seussibles.NFT
272      self.depositTible(tible: <- tible)
273    }
274
275    access(all) fun depositTible(tible: @{TiblesNFT.INFT}) {
276      pre {
277        self.ownedNFTs[tible.id] == nil: "tible with this id already exists"
278      }
279      let token <- tible as! @Seussibles.NFT
280      let id = token.id
281      self.ownedNFTs[id] <-! token
282
283      if self.owner?.address != nil {
284        emit Deposit(id: id, to: self.owner?.address)
285      }
286    }
287
288    access(all) view fun getIDs(): [UInt64] {
289      return self.ownedNFTs.keys
290    }
291
292    access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
293      return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
294    }
295
296    access(all) view  fun borrowTible(id: UInt64): &{TiblesNFT.INFT} {
297      if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
298        return nft as! &Seussibles.NFT
299      }
300      panic("Failed to borrow NFT with ID: ".concat(id.toString()))
301    }
302
303    access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
304      let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("Could not withdraw an NFT with the provided ID from the collection")
305      emit Withdraw(id: token.id, from: self.owner?.address)
306      return <-token
307    }
308
309    access(NonFungibleToken.Withdraw) fun withdrawTible(id: UInt64): @Seussibles.NFT {
310      let token <- self.ownedNFTs.remove(key: id) ?? panic("Cannot withdraw: tible does not exist in the collection")
311      let tible <- token as! @Seussibles.NFT
312      emit Withdraw(id: tible.id, from: self.owner?.address)
313      return <-tible
314    }
315
316    access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
317      if let token = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
318        return token as &{ViewResolver.Resolver}
319      }
320      return nil
321    }
322
323    access(all) fun tibleDescriptions(): {UInt64: {String: AnyStruct}} {
324      var descriptions: {UInt64: {String: AnyStruct}} = {}
325
326      for id in self.ownedNFTs.keys {
327        let ref = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}?
328        let nft = ref as! &NFT
329        var description: {String: AnyStruct} = {}
330        description["mintNumber"] = nft.mintNumber
331        description["metadata"] = nft.metadata()
332        descriptions[id] = description
333      }
334
335      return descriptions
336    }
337
338    access(NonFungibleToken.Withdraw) fun destroyTible(id: UInt64) {
339      let token <- self.withdraw(withdrawID: id)
340      emit TibleDestroyed(id: id)
341      destroy token
342    }
343
344
345    access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
346      return <-Seussibles.createEmptyCollection(nftType: Type<@Seussibles.NFT>())
347    }
348
349    init () {
350      self.ownedNFTs <- {}
351    }
352  }
353
354  access(all) view fun getContractViews(resourceType: Type?): [Type] {
355    return [
356      Type<MetadataViews.NFTCollectionData>(),
357      Type<MetadataViews.NFTCollectionDisplay>(),
358      Type<CrossVMMetadataViews.EVMPointer>()
359    ]
360  }
361
362  access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
363    let squareMedia = MetadataViews.Media(
364      file: MetadataViews.HTTPFile(url: "https://i.tibles.com/m/seussibles-flow-icon.png"),
365      mediaType: "image/svg+xml"
366    )
367    let bannerMedia = MetadataViews.Media(
368      file: MetadataViews.HTTPFile(url: "https://i.tibles.com/m/seussibles-flow-collection-banner.png"),
369      mediaType: "image/png"
370    )
371
372    let socialsData: {String: String} = {"twitter":"https://twitter.com/seussibles","discord":"https://discord.gg/tibles"}
373    let socials:{String: MetadataViews.ExternalURL } = {}
374    for key in socialsData.keys {
375      socials[key] = MetadataViews.ExternalURL(socialsData[key]!)
376    }
377
378    switch viewType {
379      case Type<MetadataViews.NFTCollectionData>():
380        let collectionData = MetadataViews.NFTCollectionData(
381          storagePath: self.CollectionStoragePath,
382          publicPath: self.PublicCollectionPath,
383          publicCollection: Type<&Seussibles.Collection>(),
384          publicLinkedType: Type<&Seussibles.Collection>(),
385          createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
386              return <-Seussibles.createEmptyCollection(nftType: Type<@Seussibles.NFT>())
387          })
388        )
389        return collectionData
390      case Type<MetadataViews.NFTCollectionDisplay>():
391        let media = MetadataViews.Media(
392          file: MetadataViews.HTTPFile(url: "https://.svg"),
393          mediaType: "image/svg+xml"
394        )
395        return MetadataViews.NFTCollectionDisplay(
396          name: "Seussibles! Dr. Seuss Collection by Tibles",
397          description: "An officially licensed Dr. Seuss digital collecting experience by Tibles. Available on iOS and web, Seussibles! is super accessible, engaging, and fun!",
398          externalURL: MetadataViews.ExternalURL("https://seussibles.com"),
399          squareImage: squareMedia,
400          bannerImage: bannerMedia,
401          socials: socials
402        )
403      case Type<CrossVMMetadataViews.EVMPointer>():
404        return CrossVMMetadataViews.EVMPointer(
405          cadenceType: Type<@Seussibles.NFT>(),
406          cadenceContractAddress: self.account.address,
407          evmContractAddress: EVM.addressFromString("0x3F013CD02C8803217366ee9587FDE1652ac5A57e"),
408          nativeVM: CrossVMMetadataViews.VM.Cadence
409      )
410    }
411    return nil
412  }
413
414  access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
415    return <- create Collection()
416  }
417
418  access(all) struct ContentLocation: TiblesProducer.ContentLocation {
419    access(all) let setId: String
420    access(all) let itemId: String
421    access(all) let variantId: String
422
423    init(setId: String, itemId: String, variantId: String) {
424      self.setId = setId
425      self.itemId = itemId
426      self.variantId = variantId
427    }
428  }
429
430  access(all) struct interface IContentLocation {}
431
432  access(all) resource Producer: TiblesProducer.IProducer, TiblesProducer.IContent {
433    access(contract) let minters: @{String: {TiblesProducer.Minter}}
434    access(contract) let contentIdsToPaths: {String: {TiblesProducer.ContentLocation}}
435    access(contract) let sets: {String: Set}
436
437    access(all) fun minter(id: String): &Minter? {
438      let ref = &self.minters[id] as &{TiblesProducer.IMinter}?
439      return ref as! &Minter?
440    }
441
442    access(all) fun set(id: String): &Set? {
443      return &self.sets[id] as &Set?
444    }
445
446    access(all) fun addSet(_ set: Set, contentCapability: Capability) {
447      pre {
448        self.sets[set.id] == nil: "Set with id: ".concat(set.id).concat(" already exists")
449      }
450
451      self.sets[set.id] = set
452
453      for item in set.items.values {
454        for variant in set.variants.values {
455          let limit: UInt32? = variant.maxCount()
456
457          let minterId: String = set.id.concat(":").concat(item.id).concat(":").concat(variant.id)
458          let minter <- create Minter(id: minterId, limit: limit, contentCapability: contentCapability)
459
460          if self.minters.keys.contains(minterId) {
461            panic("Minter ID ".concat(minterId).concat(" already exists."))
462          }
463
464          self.minters[minterId] <-! minter as! @{TiblesProducer.Minter}
465
466          let path = ContentLocation(setId: set.id, itemId: item.id, variantId: variant.id)
467          self.contentIdsToPaths[minterId] = path
468
469          emit MinterCreated(minterId: minterId)
470        }
471      }
472    }
473
474    access(all) fun getMetadata(contentId: String): {String: AnyStruct}? {
475      let path = self.contentIdsToPaths[contentId] ?? panic("Failed to get content path")
476      let location = path as! ContentLocation
477      let set = self.set(id: location.setId) ?? panic("The set does not exist!")
478      let item = set.item(location.itemId) ?? panic("Item metadata is nil")
479      let variant = set.variant(location.variantId) ?? panic("Variant metadata is nil")
480
481      var metadata: {String: AnyStruct} = {}
482      metadata["set"] = set
483      metadata["item"] = item
484      metadata["variant"] = variant
485      return metadata
486    }
487
488    init() {
489      self.sets = {}
490      self.contentIdsToPaths = {}
491      self.minters <- {}
492    }
493  }
494
495  access(all) struct Set {
496    access(all) let id: String
497    access(contract) let items: {String: Item}
498    access(contract) let variants: {String: Variant}
499    access(contract) var metadata: {String: AnyStruct}?
500
501    access(all) fun title(): String {
502      return self.metadata!["title"]! as! String
503    }
504
505    access(all) fun item(_ id: String): &Item? {
506      return &self.items[id] as &Item?
507    }
508
509    access(all) fun variant(_ id: String): &Variant? {
510      return &self.variants[id] as &Variant?
511    }
512
513    access(all) fun update(title: String) {
514      self.metadata = {
515        "title": title
516      }
517    }
518
519    init(id: String, title: String, items: {String: Item}, variants: {String: Variant}) {
520      self.id = id
521      self.items = items
522      self.variants = variants
523      self.metadata = nil
524      self.update(title: title)
525    }
526  }
527
528  access(all) struct Item {
529    access(all) let id: String
530    access(contract) var metadata: {String: AnyStruct}?
531
532    access(all) fun title(): String {
533      return self.metadata!["title"]! as! String
534    }
535
536    access(all) fun imageUrl(variantId: String): String {
537      let imageUrls = self.metadata!["imageUrls"]! as! {String: String}
538      return imageUrls[variantId]!
539    }
540
541    access(all) fun update(title: String, imageUrls: {String: String}) {
542      self.metadata = {
543        "title": title,
544        "imageUrls": imageUrls
545      }
546    }
547
548    init(id: String, title: String, imageUrls: {String: String}) {
549      self.id = id
550      self.metadata = nil
551      self.update(title: title, imageUrls: imageUrls)
552    }
553  }
554
555  access(all) struct Variant {
556    access(all) let id: String
557    access(contract) var metadata: {String: AnyStruct}?
558
559    access(all) fun title(): String {
560      return self.metadata!["title"]! as! String
561    }
562
563    access(all) fun batchSize(): UInt32? {
564      return self.metadata!["batchSize"] as! UInt32?
565    }
566
567    access(all) fun maxCount(): UInt32? {
568      return self.metadata!["maxCount"] as! UInt32?
569    }
570
571    access(all) fun update(title: String, batchSize: UInt32?, maxCount: UInt32?) {
572      assert((batchSize == nil) != (maxCount == nil), message: "batch size or max count can be used, not both")
573      let metadata: {String: AnyStruct} = {
574        "title": title
575      }
576      let previousBatchSize = (self.metadata ?? {})["batchSize"] as! UInt32?
577      let previousMaxCount = (self.metadata ?? {})["maxCount"] as! UInt32?
578      if let batchSize = batchSize {
579        assert(previousMaxCount == nil, message: "Cannot change from max count to batch size")
580        assert(previousBatchSize == nil || previousBatchSize == batchSize, message: "batch size cannot be changed once set")
581        metadata["batchSize"] = batchSize
582      }
583      if let maxCount = maxCount {
584        assert(previousBatchSize == nil, message: "Cannot change from batch size to max count")
585        assert(previousMaxCount == nil || previousMaxCount == maxCount, message: "max count cannot be changed once set")
586        metadata["maxCount"] = maxCount
587      }
588      self.metadata = metadata
589    }
590
591    init(id: String, title: String, batchSize: UInt32?, maxCount: UInt32?) {
592      self.id = id
593      self.metadata = nil
594      self.update(title: title, batchSize: batchSize, maxCount: maxCount)
595    }
596  }
597
598  access(all) resource Minter: TiblesProducer.Minter {
599    access(all) let id: String
600    access(all) var lastMintNumber: UInt32
601    access(contract) let tibles: @{UInt32: {TiblesNFT.INFT}}
602    access(all) let limit: UInt32?
603    access(all) let contentCapability: Capability
604
605    access(all) fun withdraw(mintNumber: UInt32): @{TiblesNFT.INFT} {
606      pre {
607        self.tibles[mintNumber] != nil: "The tible does not exist in this minter."
608      }
609      return <- self.tibles.remove(key: mintNumber)!
610    }
611
612    access(all) fun mintNext() {
613      if let limit = self.limit {
614        if self.lastMintNumber >= limit {
615          panic("You've hit the limit for number of tokens in this minter!")
616        }
617      }
618
619      let id = Seussibles.totalSupply + 1
620      let mintNumber = self.lastMintNumber + 1
621      let tible <- create NFT(id: id, mintNumber: mintNumber, contentCapability: self.contentCapability, contentId: self.id)
622      self.tibles[mintNumber] <-! tible
623      self.lastMintNumber = mintNumber
624      Seussibles.totalSupply = id
625
626      emit TibleMinted(minterId: self.id, mintNumber: mintNumber, id: id)
627    }
628
629    init(id: String, limit: UInt32?, contentCapability: Capability) {
630      self.id = id
631      self.lastMintNumber = 0
632      self.tibles <- {}
633      self.limit = limit
634      self.contentCapability = contentCapability
635    }
636  }
637
638  access(all) resource PackMinter: TiblesProducer.IContent{
639      access(all) let id: String
640      access(all) var lastMintNumber: UInt32
641      access(contract) let packs: @{UInt64: {TiblesNFT.INFT}}
642      access(contract) let contentIdsToPaths: {String: {TiblesProducer.ContentLocation}}
643      access(all) let contentCapability: Capability
644
645      access(all) fun getMetadata(contentId: String): {String: AnyStruct}? {
646        return {
647          "pack": "Seussibles"
648        }
649      }
650
651      access(all) fun withdraw(id: UInt64): @{TiblesNFT.INFT} {
652        pre {
653          self.packs[id] != nil: "The pack does not exist in this minter."
654        }
655        return <- self.packs.remove(key: id)!
656      }
657
658      access(all) fun mintNext(printedPackId: String) {
659        let id = Seussibles.totalSupply + 1
660        let mintNumber = self.lastMintNumber + 1
661        let pack <- create NFT(id: id, mintNumber: mintNumber, contentCapability: self.contentCapability, contentId: self.id)
662        self.packs[id] <-! pack
663        self.lastMintNumber = mintNumber
664        Seussibles.totalSupply = id
665        emit PackMinted(id: id, printedPackId: printedPackId)
666      }
667
668      init(id: String, contentCapability: Capability) {
669        self.id = id
670        self.lastMintNumber = 0
671        self.packs <- {}
672        self.contentCapability = contentCapability
673        self.contentIdsToPaths = {}
674        emit PackMinterCreated(minterId: self.id)
675      }
676    }
677
678    access(all) fun createNewPackMinter(id: String, contentCapability: Capability): @PackMinter {
679      assert(self.account.address == 0x321d8fcde05f6e8c, message: "wrong address")
680      return <- create PackMinter(id: id, contentCapability: contentCapability)
681    }
682
683  init() {
684    self.totalSupply = 0
685
686    self.appId = "com.tibles.seussibles"
687    self.title = "Seussibles"
688    self.description = "Dr. Seuss officially licensed digital collectibles"
689
690    self.ProducerStoragePath = /storage/TiblesSeussiblesProducer
691    self.ProducerPath = /private/TiblesSeussiblesProducer
692    self.ContentPath = /public/TiblesSeussiblesContent
693    self.CollectionStoragePath = /storage/TiblesSeussiblesCollection
694    self.PublicCollectionPath = /public/TiblesSeussiblesCollection
695
696    let producer <- create Producer()
697    self.account.storage.save(<-producer, to: self.ProducerStoragePath)
698
699    let cap = self.account.capabilities.storage.issue<&Producer>(self.ProducerStoragePath)
700    self.account.capabilities.publish(cap, at: self.ContentPath)
701    self.contentCapability = self.account.capabilities.get<&Producer>(self.ContentPath)
702
703    emit ContractInitialized()
704  }
705}
706