Smart Contract

OneShots

A.4f7ff543c936072b.OneShots

Valid From

117,592,623

Deployed

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

Dependents

69 imports
1/*
2The following terms apply to purchasers of NFTs that contain Valiant Entertainment, LLC (“Valiant”) 
3content. As between you and Valiant you agree that all rights, title and interest (including all 
4copyright, trademark, service marks, and any and all intellectual property rights of any kind, 
5whether registered or unregistered) to the artworks, images, videos, animations, design, drawings, 
6logos and/or other digital content (“Digital Assets”) featured on and/or within this NFT are the 
7sole and exclusive property of Valiant.
8
9Subject to your rightful and lawful purchase of this NFT, Valiant grants you a personal, non-
10commercial, non-exclusive, non-transferable, non-sublicensable, revocable, limited license to 
11download, view, and display the Digital Assets on and/or within this NFT. As such, you agree that 
12you shall not: (i) modify, distort or perform any other change to the Digital Assets in any way, 
13(ii) use the Digital Assets as a brand or trademark or to advertise, market, or sell any third 
14party product or service; (iii) use the Digital Assets in connection with images, videos, or other 
15forms of media that depict hatred, intolerance, violence, cruelty, or anything else that could 
16reasonably be found to constitute hate speech or otherwise infringe upon the rights of others or 
17promote illegal activities, or reflect negatively on Valiant; (iv) use the Digital Assets in movies, 
18videos, or any other forms of media, except solely for your own personal, non-commercial use; 
19(v) sell, distribute for commercial gain (including, without limitation, giving away in the hopes 
20of eventual commercial gain), or otherwise commercialize merchandise that includes, contains, or 
21consists of the Digital Assets; (vi) attempt to trademark, copyright, or otherwise acquire additional 
22intellectual property rights in or to the Digital Assets; (vii) use the Digital Assets in connection 
23with defamatory or dishonest statements about Valiant and/or its affiliated companies or which 
24otherwise damage the goodwill, value or reputation of Valiant or represent or imply that your 
25exercise of the license granted to you herein is endorsed by Valiant and/or our affiliated companies; 
26or (viii) otherwise utilize the Digital Assets for your or any third party’s commercial benefit. 
27Notwithstanding the above prohibitions, these restrictions do not impede your ability to re-sell, 
28donate and/or transfer ownership of this NFT through the OneShots mobile application and/or website 
29and/or through secondary NFT marketplaces (e.g. OpenSea).
30
31For the avoidance of doubt, the limited license granted herein applies only to the extent that you 
32continue to own the NFT. If at any time you re-sell, transfer or donate this NFT to another party, 
33your rights under this limited licensed will immediately be revoked and you will have no further 
34rights in and/or to the Digital Assets.
35*/
36import MetadataViews from 0x1d7e57aa55817448
37import ViewResolver from 0x1d7e57aa55817448
38import NonFungibleToken from 0x1d7e57aa55817448
39import FungibleToken from 0xf233dcee88fe0abe
40import TiblesApp from 0x5cdeb067561defcb
41import TiblesNFT from 0x5cdeb067561defcb
42import TiblesProducer from 0x5cdeb067561defcb
43import CrossVMMetadataViews from 0x1d7e57aa55817448
44import EVM from 0xe467b9dd11fa00df
45
46access(all) contract OneShots:
47  NonFungibleToken,
48  TiblesApp,
49  TiblesNFT,
50  TiblesProducer
51{
52  access(all) let appId: String
53  access(all) let title: String
54  access(all) let description: String
55  access(all) let ProducerStoragePath: StoragePath
56  access(all) let ProducerPath: PrivatePath
57  access(all) let ContentPath: PublicPath
58  access(all) let contentCapability: Capability
59  access(all) let CollectionStoragePath: StoragePath
60  access(all) let PublicCollectionPath: PublicPath
61
62  access(all) event ContractInitialized()
63  access(all) event Withdraw(id: UInt64, from: Address?)
64  access(all) event Deposit(id: UInt64, to: Address?)
65  access(all) event MinterCreated(minterId: String)
66  access(all) event TibleMinted(minterId: String, mintNumber: UInt32, id: UInt64)
67  access(all) event TibleDestroyed(id: UInt64)
68  access(all) event PackMinterCreated(minterId: String)
69  access(all) event PackMinted(id: UInt64, printedPackId: String)
70
71  access(all) var totalSupply: UInt64
72
73  access(all) resource NFT: NonFungibleToken.NFT, TiblesNFT.INFT {
74    access(all) let id: UInt64
75    access(all) let mintNumber: UInt32
76
77    access(self) let contentCapability: Capability
78    access(self) let contentId: String
79
80    init(id: UInt64, mintNumber: UInt32, contentCapability: Capability, contentId: String) {
81      self.id = id
82      self.mintNumber = mintNumber
83      self.contentId = contentId
84      self.contentCapability = contentCapability
85    }
86
87    access(all) fun metadata(): {String: AnyStruct}? {
88      let content = self.contentCapability.borrow<&{TiblesProducer.IContent}>() ?? panic("Failed to borrow content provider")
89      return content.getMetadata(contentId: self.contentId)
90    }
91
92    access(all) fun displayData(): {String: String} {
93      let metadata = self.metadata() ?? panic("Missing NFT metadata")
94
95      if (metadata.containsKey("pack")) {
96        return {
97          "name": "OneShots pack",
98          "description": "A OneShots pack",
99          "imageUrl": "https://i.tibles.com/m/oneshots-flow-icon.png"
100        }
101      }
102
103      let set = metadata["set"]! as! &OneShots.Set
104      let item = metadata["item"]! as! &OneShots.Item
105      let variant = metadata["variant"]! as! &OneShots.Variant
106
107      var edition: String = ""
108      var serialInfo: String = ""
109      if let maxCount = variant.maxCount() {
110        edition = "Limited Edition"
111        serialInfo = "LE | "
112          .concat(variant.title())
113          .concat(" #")
114          .concat(self.mintNumber.toString())
115          .concat("/")
116          .concat(maxCount.toString())
117      } else if let batchSize = variant.batchSize() {
118        edition = "Standard Edition"
119        let mintSeries = (self.mintNumber - 1) / batchSize + 1
120        serialInfo = "S".concat(mintSeries.toString())
121          .concat(" | ")
122          .concat(variant.title())
123          .concat(" #")
124          .concat(self.mintNumber.toString())
125      } else {
126        panic("Missing batch size and max count")
127      }
128
129      let description = serialInfo
130        .concat("\n")
131        .concat(edition)
132        .concat("\n")
133        .concat(set.title())
134
135      let imageUrl = item.imageUrl(variantId: variant.id)
136
137      return {
138        "name": item.title(),
139        "description": description,
140        "imageUrl": imageUrl,
141        "edition": edition,
142        "serialInfo": serialInfo
143      }
144    }
145
146    access(all) fun display(): MetadataViews.Display {
147      let nftData = self.displayData()
148
149      return MetadataViews.Display(
150        name: nftData["name"] ?? "",
151        description: nftData["description"] ?? "",
152        thumbnail: MetadataViews.HTTPFile(url: nftData["imageUrl"] ?? "")
153      )
154    }
155
156    access(all) fun editions(): MetadataViews.Editions {
157      let nftData = self.displayData()
158      let metadata = self.metadata() ?? panic("Missing NFT metadata")
159      if (metadata.containsKey("pack")) {
160         return MetadataViews.Editions([MetadataViews.Edition(name: "OneShots pack", number: UInt64(self.mintNumber), max: nil)])
161      }
162
163      let variant = metadata["variant"]! as! &OneShots.Variant
164
165      var maxCount: UInt64? = nil
166      if let count = variant.maxCount() {
167        maxCount = UInt64(count)
168      }
169
170      let editionInfo = MetadataViews.Edition(
171        name: nftData["edition"] ?? "",
172        number: UInt64(self.mintNumber),
173        max: maxCount
174      )
175
176      let editionList: [MetadataViews.Edition] = [editionInfo]
177      return MetadataViews.Editions(editionList)
178    }
179
180    access(all) fun serial(): MetadataViews.Serial {
181      return MetadataViews.Serial(UInt64(self.mintNumber))
182    }
183
184    access(all) view fun royalties(): MetadataViews.Royalties {
185      let feeRecCap = getAccount(0x1f590411eaca135f)
186        .capabilities
187        .get<&{FungibleToken.Receiver}>(/public/dapperUtilityCoinReceiver)
188
189      return MetadataViews.Royalties([
190        MetadataViews.Royalty(
191          receiver: feeRecCap,
192          cut: UFix64(0.025), // 2.5% royalty
193          description: "OneShots DUC Royalty",
194        )
195      ])
196    }
197
198    access(all) fun externalURL(): MetadataViews.ExternalURL {
199      return MetadataViews.ExternalURL("https://app.oneshots.com/collection".concat(self.id.toString()))
200    }
201
202    access(all) fun nftCollectionData(): MetadataViews.NFTCollectionData {
203      return MetadataViews.NFTCollectionData(
204        storagePath: OneShots.CollectionStoragePath,
205        publicPath: OneShots.PublicCollectionPath,
206        publicCollection: Type<&OneShots.Collection>(),
207        publicLinkedType: Type<&OneShots.Collection>(),
208        createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
209          return <-OneShots.createEmptyCollection(nftType: Type<@OneShots.NFT>())
210        })
211      )
212    }
213
214    access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
215       return <-OneShots.createEmptyCollection(nftType: Type<@OneShots.NFT>())
216    }
217
218    access(all) fun nftCollectionDisplay(): MetadataViews.NFTCollectionDisplay {
219      let squareMedia = MetadataViews.Media(
220        file: MetadataViews.HTTPFile(url: "https://i.tibles.com/m/oneshots-flow-icon.png"),
221        mediaType: "image/svg+xml"
222      )
223      let bannerMedia = MetadataViews.Media(
224        file: MetadataViews.HTTPFile(url: "https://i.tibles.com/m/oneshots-flow-collection-banner.png"),
225        mediaType: "image/png"
226      )
227
228      let socialsData: {String: String} = {"twitter": "https://twitter.com/oneshotstibles"}
229      let socials:{String: MetadataViews.ExternalURL } = {}
230      for key in socialsData.keys {
231        socials[key] = MetadataViews.ExternalURL(socialsData[key]!)
232      }
233
234      return MetadataViews.NFTCollectionDisplay(
235        name: "OneShots Comic Book Trading Cards by Tibles",
236        description: "OneShots is a digital trading card collecting experience by Tibles, made just for comic book fans, backed by the FLOW blockchain.",
237        externalURL: MetadataViews.ExternalURL("https://app.oneshots.com"),
238        squareImage: squareMedia,
239        bannerImage: bannerMedia,
240        socials: socials
241      )
242    }
243
244    access(all) view fun traits(): MetadataViews.Traits {
245      let traits : [MetadataViews.Trait] = []
246      return MetadataViews.Traits(traits)
247    }
248
249    access(all) view fun getViews(): [Type] {
250      return [
251        Type<MetadataViews.Display>(),
252        Type<MetadataViews.Royalties>(),
253        Type<MetadataViews.Editions>(),
254        Type<MetadataViews.ExternalURL>(),
255        Type<MetadataViews.NFTCollectionData>(),
256        Type<MetadataViews.NFTCollectionDisplay>(),
257        Type<MetadataViews.Serial>(),
258        Type<MetadataViews.Traits>(),
259        Type<CrossVMMetadataViews.EVMPointer>()
260      ]
261    }
262
263    access(all) fun resolveView(_ view: Type): AnyStruct? {
264      switch view {
265        case Type<MetadataViews.Display>():
266          return self.display()
267        case Type<MetadataViews.Editions>():
268          return self.editions()
269        case Type<MetadataViews.Serial>():
270          return self.serial()
271        case Type<MetadataViews.Royalties>():
272          return self.royalties()
273        case Type<MetadataViews.ExternalURL>():
274          return self.externalURL()
275        case Type<MetadataViews.NFTCollectionData>():
276          return self.nftCollectionData()
277        case Type<MetadataViews.NFTCollectionDisplay>():
278          return self.nftCollectionDisplay()
279        case Type<MetadataViews.Traits>():
280          return self.traits()
281        case Type<CrossVMMetadataViews.EVMPointer>():
282          return OneShots.resolveContractView(resourceType: nil, viewType: Type<CrossVMMetadataViews.EVMPointer>())
283        default: return nil
284      }
285    }
286  }
287
288  access(all) resource Collection:
289    NonFungibleToken.Collection,
290    TiblesNFT.CollectionPublic
291  {
292    access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
293
294    access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
295      let supportedTypes: {Type: Bool} = {}
296      supportedTypes[Type<@OneShots.NFT>()] = true
297      return supportedTypes
298    }
299
300    access(all) view fun isSupportedNFTType(type: Type): Bool {
301      return type == Type<@OneShots.NFT>()
302    }
303
304    access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
305      let tible <- token as! @OneShots.NFT
306      self.depositTible(tible: <- tible)
307    }
308
309    access(all) fun depositTible(tible: @{TiblesNFT.INFT}) {
310      pre {
311        self.ownedNFTs[tible.id] == nil: "tible with this id already exists"
312      }
313      let token <- tible as! @OneShots.NFT
314      let id = token.id
315      self.ownedNFTs[id] <-! token
316
317      if self.owner?.address != nil {
318        emit Deposit(id: id, to: self.owner?.address)
319      }
320    }
321
322    access(all) view fun getIDs(): [UInt64] {
323      return self.ownedNFTs.keys
324    }
325
326    access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
327      return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
328    }
329
330    access(all) view  fun borrowTible(id: UInt64): &{TiblesNFT.INFT} {
331      if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
332        return nft as! &OneShots.NFT
333      }
334      panic("Failed to borrow NFT with ID: ".concat(id.toString()))
335    }
336
337    access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
338      let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("Could not withdraw an NFT with the provided ID from the collection")
339      emit Withdraw(id: token.id, from: self.owner?.address)
340      return <-token
341    }
342
343    access(NonFungibleToken.Withdraw) fun withdrawTible(id: UInt64): @OneShots.NFT {
344      let token <- self.ownedNFTs.remove(key: id) ?? panic("Cannot withdraw: tible does not exist in the collection")
345      let tible <- token as! @OneShots.NFT
346      emit Withdraw(id: tible.id, from: self.owner?.address)
347      return <-tible
348    }
349
350    access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
351      if let token = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
352        return token as &{ViewResolver.Resolver}
353      }
354      return nil
355    }
356
357    access(all) fun tibleDescriptions(): {UInt64: {String: AnyStruct}} {
358      var descriptions: {UInt64: {String: AnyStruct}} = {}
359
360      for id in self.ownedNFTs.keys {
361        let ref = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}?
362        let nft = ref as! &NFT
363        var description: {String: AnyStruct} = {}
364        description["mintNumber"] = nft.mintNumber
365        description["metadata"] = nft.metadata()
366        descriptions[id] = description
367      }
368
369      return descriptions
370    }
371
372    access(NonFungibleToken.Withdraw) fun destroyTible(id: UInt64) {
373      let token <- self.withdraw(withdrawID: id)
374      emit TibleDestroyed(id: id)
375      destroy token
376    }
377
378
379    access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
380      return <-OneShots.createEmptyCollection(nftType: Type<@OneShots.NFT>())
381    }
382
383    init () {
384      self.ownedNFTs <- {}
385    }
386  }
387
388  access(all) view fun getContractViews(resourceType: Type?): [Type] {
389    return [
390      Type<MetadataViews.NFTCollectionData>(),
391      Type<MetadataViews.NFTCollectionDisplay>(),
392      Type<CrossVMMetadataViews.EVMPointer>()
393    ]
394  }
395
396  access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
397    let squareMedia = MetadataViews.Media(
398      file: MetadataViews.HTTPFile(url: "https://i.tibles.com/m/oneshots-flow-icon.png"),
399      mediaType: "image/svg+xml"
400    )
401    let bannerMedia = MetadataViews.Media(
402      file: MetadataViews.HTTPFile(url: "https://i.tibles.com/m/oneshots-flow-collection-banner.png"),
403      mediaType: "image/png"
404    )
405
406    let socialsData: {String: String} = {"twitter": "https://twitter.com/oneshotstibles"}
407    let socials:{String: MetadataViews.ExternalURL } = {}
408    for key in socialsData.keys {
409      socials[key] = MetadataViews.ExternalURL(socialsData[key]!)
410    }
411
412    switch viewType {
413      case Type<MetadataViews.NFTCollectionData>():
414        let collectionData = MetadataViews.NFTCollectionData(
415          storagePath: self.CollectionStoragePath,
416          publicPath: self.PublicCollectionPath,
417          publicCollection: Type<&OneShots.Collection>(),
418          publicLinkedType: Type<&OneShots.Collection>(),
419          createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
420              return <-OneShots.createEmptyCollection(nftType: Type<@OneShots.NFT>())
421          })
422        )
423        return collectionData
424      case Type<MetadataViews.NFTCollectionDisplay>():
425        let media = MetadataViews.Media(
426          file: MetadataViews.HTTPFile(url: "https://.svg"),
427          mediaType: "image/svg+xml"
428        )
429        return MetadataViews.NFTCollectionDisplay(
430          name: "OneShots Comic Book Trading Cards by Tibles",
431          description: "OneShots is a digital trading card collecting experience by Tibles, made just for comic book fans, backed by the FLOW blockchain.",
432          externalURL: MetadataViews.ExternalURL("https://app.oneshots.com"),
433          squareImage: squareMedia,
434          bannerImage: bannerMedia,
435          socials: socials
436        )
437      case Type<CrossVMMetadataViews.EVMPointer>():
438        return CrossVMMetadataViews.EVMPointer(
439          cadenceType: Type<@OneShots.NFT>(),
440          cadenceContractAddress: self.account.address,
441          evmContractAddress: EVM.addressFromString("0xb266949A9d976d0e52cC746C2676C103757C8e11"),
442          nativeVM: CrossVMMetadataViews.VM.Cadence
443      )
444    }
445    return nil
446  }
447
448  access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
449    return <- create Collection()
450  }
451
452  access(all) struct ContentLocation: TiblesProducer.ContentLocation {
453    access(all) let setId: String
454    access(all) let itemId: String
455    access(all) let variantId: String
456
457    init(setId: String, itemId: String, variantId: String) {
458      self.setId = setId
459      self.itemId = itemId
460      self.variantId = variantId
461    }
462  }
463
464  access(all) struct interface IContentLocation {}
465
466  access(all) resource Producer: TiblesProducer.IProducer, TiblesProducer.IContent {
467    access(contract) let minters: @{String: {TiblesProducer.Minter}}
468    access(contract) let contentIdsToPaths: {String: {TiblesProducer.ContentLocation}}
469    access(contract) let sets: {String: Set}
470
471    access(all) fun minter(id: String): &Minter? {
472      let ref = &self.minters[id] as &{TiblesProducer.IMinter}?
473      return ref as! &Minter?
474    }
475
476    access(all) fun set(id: String): &Set? {
477      return &self.sets[id] as &Set?
478    }
479
480    access(all) fun addSet(_ set: Set, contentCapability: Capability) {
481      pre {
482        self.sets[set.id] == nil: "Set with id: ".concat(set.id).concat(" already exists")
483      }
484
485      self.sets[set.id] = set
486
487      for item in set.items.values {
488        for variant in set.variants.values {
489          let limit: UInt32? = variant.maxCount()
490
491          let minterId: String = set.id.concat(":").concat(item.id).concat(":").concat(variant.id)
492          let minter <- create Minter(id: minterId, limit: limit, contentCapability: contentCapability)
493
494          if self.minters.keys.contains(minterId) {
495            panic("Minter ID ".concat(minterId).concat(" already exists."))
496          }
497
498          self.minters[minterId] <-! minter as! @{TiblesProducer.Minter}
499
500          let path = ContentLocation(setId: set.id, itemId: item.id, variantId: variant.id)
501          self.contentIdsToPaths[minterId] = path
502
503          emit MinterCreated(minterId: minterId)
504        }
505      }
506    }
507
508    access(all) fun getMetadata(contentId: String): {String: AnyStruct}? {
509      let path = self.contentIdsToPaths[contentId] ?? panic("Failed to get content path")
510      let location = path as! ContentLocation
511      let set = self.set(id: location.setId) ?? panic("The set does not exist!")
512      let item = set.item(location.itemId) ?? panic("Item metadata is nil")
513      let variant = set.variant(location.variantId) ?? panic("Variant metadata is nil")
514
515      var metadata: {String: AnyStruct} = {}
516      metadata["set"] = set
517      metadata["item"] = item
518      metadata["variant"] = variant
519      return metadata
520    }
521
522    init() {
523      self.sets = {}
524      self.contentIdsToPaths = {}
525      self.minters <- {}
526    }
527  }
528
529  access(all) struct Set {
530    access(all) let id: String
531    access(contract) let items: {String: Item}
532    access(contract) let variants: {String: Variant}
533    access(contract) var metadata: {String: AnyStruct}?
534
535    access(all) fun title(): String {
536      return self.metadata!["title"]! as! String
537    }
538
539    access(all) fun item(_ id: String): &Item? {
540      return &self.items[id] as &Item?
541    }
542
543    access(all) fun variant(_ id: String): &Variant? {
544      return &self.variants[id] as &Variant?
545    }
546
547    access(all) fun update(title: String) {
548      self.metadata = {
549        "title": title
550      }
551    }
552
553    init(id: String, title: String, items: {String: Item}, variants: {String: Variant}) {
554      self.id = id
555      self.items = items
556      self.variants = variants
557      self.metadata = nil
558      self.update(title: title)
559    }
560  }
561
562  access(all) struct Item {
563    access(all) let id: String
564    access(contract) var metadata: {String: AnyStruct}?
565
566    access(all) fun title(): String {
567      return self.metadata!["title"]! as! String
568    }
569
570    access(all) fun imageUrl(variantId: String): String {
571      let imageUrls = self.metadata!["imageUrls"]! as! {String: String}
572      return imageUrls[variantId]!
573    }
574
575    access(all) fun update(title: String, imageUrls: {String: String}) {
576      self.metadata = {
577        "title": title,
578        "imageUrls": imageUrls
579      }
580    }
581
582    init(id: String, title: String, imageUrls: {String: String}) {
583      self.id = id
584      self.metadata = nil
585      self.update(title: title, imageUrls: imageUrls)
586    }
587  }
588
589  access(all) struct Variant {
590    access(all) let id: String
591    access(contract) var metadata: {String: AnyStruct}?
592
593    access(all) fun title(): String {
594      return self.metadata!["title"]! as! String
595    }
596
597    access(all) fun batchSize(): UInt32? {
598      return self.metadata!["batchSize"] as! UInt32?
599    }
600
601    access(all) fun maxCount(): UInt32? {
602      return self.metadata!["maxCount"] as! UInt32?
603    }
604
605    access(all) fun update(title: String, batchSize: UInt32?, maxCount: UInt32?) {
606      assert((batchSize == nil) != (maxCount == nil), message: "batch size or max count can be used, not both")
607      let metadata: {String: AnyStruct} = {
608        "title": title
609      }
610      let previousBatchSize = (self.metadata ?? {})["batchSize"] as! UInt32?
611      let previousMaxCount = (self.metadata ?? {})["maxCount"] as! UInt32?
612      if let batchSize = batchSize {
613        assert(previousMaxCount == nil, message: "Cannot change from max count to batch size")
614        assert(previousBatchSize == nil || previousBatchSize == batchSize, message: "batch size cannot be changed once set")
615        metadata["batchSize"] = batchSize
616      }
617      if let maxCount = maxCount {
618        assert(previousBatchSize == nil, message: "Cannot change from batch size to max count")
619        assert(previousMaxCount == nil || previousMaxCount == maxCount, message: "max count cannot be changed once set")
620        metadata["maxCount"] = maxCount
621      }
622      self.metadata = metadata
623    }
624
625    init(id: String, title: String, batchSize: UInt32?, maxCount: UInt32?) {
626      self.id = id
627      self.metadata = nil
628      self.update(title: title, batchSize: batchSize, maxCount: maxCount)
629    }
630  }
631
632  access(all) resource Minter: TiblesProducer.Minter {
633    access(all) let id: String
634    access(all) var lastMintNumber: UInt32
635    access(contract) let tibles: @{UInt32: {TiblesNFT.INFT}}
636    access(all) let limit: UInt32?
637    access(all) let contentCapability: Capability
638
639    access(all) fun withdraw(mintNumber: UInt32): @{TiblesNFT.INFT} {
640      pre {
641        self.tibles[mintNumber] != nil: "The tible does not exist in this minter."
642      }
643      return <- self.tibles.remove(key: mintNumber)!
644    }
645
646    access(all) fun mintNext() {
647      if let limit = self.limit {
648        if self.lastMintNumber >= limit {
649          panic("You've hit the limit for number of tokens in this minter!")
650        }
651      }
652
653      let id = OneShots.totalSupply + 1
654      let mintNumber = self.lastMintNumber + 1
655      let tible <- create NFT(id: id, mintNumber: mintNumber, contentCapability: self.contentCapability, contentId: self.id)
656      self.tibles[mintNumber] <-! tible
657      self.lastMintNumber = mintNumber
658      OneShots.totalSupply = id
659
660      emit TibleMinted(minterId: self.id, mintNumber: mintNumber, id: id)
661    }
662
663    init(id: String, limit: UInt32?, contentCapability: Capability) {
664      self.id = id
665      self.lastMintNumber = 0
666      self.tibles <- {}
667      self.limit = limit
668      self.contentCapability = contentCapability
669    }
670  }
671
672  access(all) resource PackMinter: TiblesProducer.IContent{
673      access(all) let id: String
674      access(all) var lastMintNumber: UInt32
675      access(contract) let packs: @{UInt64: {TiblesNFT.INFT}}
676      access(contract) let contentIdsToPaths: {String: {TiblesProducer.ContentLocation}}
677      access(all) let contentCapability: Capability
678
679      access(all) fun getMetadata(contentId: String): {String: AnyStruct}? {
680        return {
681          "pack": "OneShots"
682        }
683      }
684
685      access(all) fun withdraw(id: UInt64): @{TiblesNFT.INFT} {
686        pre {
687          self.packs[id] != nil: "The pack does not exist in this minter."
688        }
689        return <- self.packs.remove(key: id)!
690      }
691
692      access(all) fun mintNext(printedPackId: String) {
693        let id = OneShots.totalSupply + 1
694        let mintNumber = self.lastMintNumber + 1
695        let pack <- create NFT(id: id, mintNumber: mintNumber, contentCapability: self.contentCapability, contentId: self.id)
696        self.packs[id] <-! pack
697        self.lastMintNumber = mintNumber
698        OneShots.totalSupply = id
699        emit PackMinted(id: id, printedPackId: printedPackId)
700      }
701
702      init(id: String, contentCapability: Capability) {
703        self.id = id
704        self.lastMintNumber = 0
705        self.packs <- {}
706        self.contentCapability = contentCapability
707        self.contentIdsToPaths = {}
708        emit PackMinterCreated(minterId: self.id)
709      }
710    }
711
712    access(all) fun createNewPackMinter(id: String, contentCapability: Capability): @PackMinter {
713      assert(self.account.address == 0x4f7ff543c936072b, message: "wrong address")
714      return <- create PackMinter(id: id, contentCapability: contentCapability)
715    }
716
717  init() {
718    self.totalSupply = 0
719
720    self.appId = "com.tibles.oneshots"
721    self.title = "One Shots"
722    self.description = "OneShots is a digital trading card collecting experience by Tibles, made just for comic book fans, backed by the FLOW blockchain."
723
724    self.ProducerStoragePath = /storage/TiblesOneShotsProducer
725    self.ProducerPath = /private/TiblesOneShotsProducer
726    self.ContentPath = /public/TiblesOneShotsContent
727    self.CollectionStoragePath = /storage/TiblesOneShotsCollection
728    self.PublicCollectionPath = /public/TiblesOneShotsCollection
729
730    let producer <- create Producer()
731    self.account.storage.save(<-producer, to: self.ProducerStoragePath)
732
733    let cap = self.account.capabilities.storage.issue<&Producer>(self.ProducerStoragePath)
734    self.account.capabilities.publish(cap, at: self.ContentPath)
735    self.contentCapability = self.account.capabilities.get<&Producer>(self.ContentPath)
736
737    emit ContractInitialized()
738  }
739}
740