Smart Contract

PackNFT

A.c6945445cdbefec9.PackNFT

Deployed

2w ago
Feb 13, 2026, 03:25:06 AM UTC

Dependents

18 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import MetadataViews from 0x1d7e57aa55817448
3import ViewResolver from 0x1d7e57aa55817448
4
5access(all) contract PackNFT: NonFungibleToken {
6
7    access(all) var totalSupply: UInt64
8    access(all) let itemEditions: {UInt64: UInt32}
9    access(all) var version: String
10    access(all) let CollectionStoragePath: StoragePath
11    access(all) let CollectionPublicPath: PublicPath
12    access(all) var CollectionPublicType: Type
13    access(all) let OperatorStoragePath: StoragePath
14
15    access(all) var defaultCollectionMetadata: CollectionMetadata?
16    access(contract) let itemMetadata: {String: Metadata}
17    access(contract) let itemCollectionMetadata: {String: CollectionMetadata}
18
19    access(all) var metadataOpenedWarning: String
20
21    // representation of the NFT in this contract to keep track of states
22    access(contract) let packs: @{UInt64: Pack}
23
24    access(all) event RevealRequest(id: UInt64, openRequest: Bool)
25    access(all) event OpenRequest(id: UInt64)
26    access(all) event Revealed(id: UInt64, salt: String, nfts: String)
27    access(all) event Opened(id: UInt64)
28    access(all) event MetadataUpdated(distId: UInt64, edition: UInt32?, metadata: Metadata)
29    access(all) event Mint(id: UInt64, edition: UInt32, commitHash: String, distId: UInt64, nftCount: UInt16?, lockTime: UFix64?, additionalInfo: {String: String}?)
30    access(all) event ContractInitialized()
31    access(all) event Withdraw(id: UInt64, from: Address?)
32    access(all) event Deposit(id: UInt64, to: Address?)
33
34    access(all) enum Status: UInt8 {
35        access(all) case Sealed
36        access(all) case Revealed
37        access(all) case Opened
38    }
39
40    access(all) resource interface IOperator {
41        access(all) fun setMetadata(
42            distId: UInt64,
43            edition: UInt32?,
44            metadata: Metadata,
45            overwrite: Bool
46         )
47         access(all) fun mint(
48            distId: UInt64,
49            additionalInfo: {String: String}?,
50            commitHash: String,
51            issuer: Address,
52            nftCount: UInt16?,
53            lockTime: UFix64?
54         ): @NFT
55        access(all) fun reveal(id: UInt64, nfts: [&{NonFungibleToken.NFT}], salt: String)
56        access(all) fun open(id: UInt64, nfts: [&{NonFungibleToken.NFT}])
57    }
58
59    access(all) resource PackNFTOperator: IOperator {
60
61         access(all) fun setMetadata(
62            distId: UInt64,
63            edition: UInt32?,
64            metadata: Metadata,
65            overwrite: Bool
66         ) {
67            let fullId = edition != nil ? distId.toString().concat(":").concat(edition!.toString()) : distId.toString()
68            if !overwrite && PackNFT.itemMetadata[fullId] != nil {
69                return
70            }
71            PackNFT.itemMetadata[fullId] = metadata
72            emit MetadataUpdated(distId: distId, edition: edition, metadata: metadata)
73
74         }
75
76         access(all) fun mint(
77            distId: UInt64,
78            additionalInfo: {String: String}?,
79            commitHash: String,
80            issuer: Address,
81            nftCount: UInt16?,
82            lockTime: UFix64?
83         ): @NFT{
84            assert(PackNFT.defaultCollectionMetadata != nil, message: "Please set the default collection metadata before minting")
85
86            let totalEditions = PackNFT.itemEditions[distId] ?? UInt32(0)
87            let edition = totalEditions + UInt32(1)
88            let id = PackNFT.totalSupply + 1
89            let nft <- create NFT(id: id, distId: distId, edition: edition, additionalInfo: additionalInfo, commitHash: commitHash, issuer: issuer, nftCount: nftCount, lockTime: lockTime)
90            PackNFT.itemEditions[distId] = edition
91            PackNFT.totalSupply = PackNFT.totalSupply + 1
92            let p  <-create Pack(commitHash: commitHash, issuer: issuer, nftCount: nftCount, lockTime: lockTime)
93            PackNFT.packs[id] <-! p
94            emit Mint(id: id, edition: edition, commitHash: commitHash, distId: distId, nftCount: nftCount, lockTime: lockTime, additionalInfo: additionalInfo)
95            return <- nft
96         }
97
98        access(all) fun reveal(id: UInt64, nfts: [&{NonFungibleToken.NFT}], salt: String) {
99            let p <- PackNFT.packs.remove(key: id) ?? panic("no such pack")
100            p.reveal(id: id, nfts: nfts, salt: salt)
101            PackNFT.packs[id] <-! p
102        }
103
104        access(all) fun open(id: UInt64, nfts: [&{NonFungibleToken.NFT}]) {
105            let p <- PackNFT.packs.remove(key: id) ?? panic("no such pack")
106            p.open(id: id, nfts: nfts)
107            PackNFT.packs[id] <-! p
108        }
109
110        access(all) fun createOperator(): @PackNFTOperator {
111            return <- create PackNFTOperator()
112        }
113
114        access(all) fun setDefaultCollectionMetadata(defaultCollectionMetadata: CollectionMetadata) {
115            PackNFT.defaultCollectionMetadata = defaultCollectionMetadata
116        }
117
118        access(all) fun setVersion(version: String) {
119            PackNFT.version = version
120        }
121
122        init(){}
123    }
124
125    access(all) resource Pack {
126        access(all) let commitHash: String
127        access(all) let issuer: Address
128        access(all) let nftCount: UInt16?
129        access(all) let lockTime: UFix64?
130        access(all) var status: Status
131        access(all) var salt: String?
132
133        access(all) fun verify(nftString: String): Bool {
134            assert(self.status as! PackNFT.Status != PackNFT.Status.Sealed, message: "Pack not revealed yet")
135            var hashString = self.salt!
136            hashString = hashString.concat(",").concat(nftString)
137            let hash = HashAlgorithm.SHA2_256.hash(hashString.utf8)
138            assert(self.commitHash == String.encodeHex(hash), message: "CommitHash was not verified")
139            return true
140        }
141
142        access(self) fun _hashNft(nft: &{NonFungibleToken.NFT}): String {
143            return nft.getType().identifier.concat(".").concat(nft.id.toString())
144        }
145
146        access(self) fun _verify(nfts: [&{NonFungibleToken.NFT}], salt: String, commitHash: String): String {
147            assert(self.nftCount == nil || self.nftCount! == (UInt16(nfts.length)), message: "nftCount doesn't match nfts length")
148            var hashString = salt.concat(",").concat(nfts.length.toString())
149            var nftString = nfts.length > 0 ? self._hashNft(nft: nfts[0]) : ""
150            var i = 1
151            while i < nfts.length {
152                let s = self._hashNft(nft: nfts[i])
153                nftString = nftString.concat(",").concat(s)
154                i = i + 1
155            }
156            hashString = hashString.concat(",").concat(nftString)
157            let hash = HashAlgorithm.SHA2_256.hash(hashString.utf8)
158            assert(self.commitHash == String.encodeHex(hash), message: "CommitHash was not verified")
159            return nftString
160        }
161
162        access(contract) fun reveal(id: UInt64, nfts: [&{NonFungibleToken.NFT}], salt: String) {
163            assert(self.status as! PackNFT.Status == PackNFT.Status.Sealed, message: "Pack status is not Sealed")
164            let v = self._verify(nfts: nfts, salt: salt, commitHash: self.commitHash)
165            self.salt = salt
166            self.status = PackNFT.Status.Revealed
167            emit Revealed(id: id, salt: salt, nfts: v)
168        }
169
170        access(contract) fun open(id: UInt64, nfts: [&{NonFungibleToken.NFT}]) {
171            pre {
172                (self.lockTime == nil) || (getCurrentBlock().timestamp > self.lockTime!): "Pack is locked until ".concat(self.lockTime!.toString())
173            }
174            assert(self.status as! PackNFT.Status == PackNFT.Status.Revealed, message: "Pack status is not Revealed")
175            self._verify(nfts: nfts, salt: self.salt!, commitHash: self.commitHash)
176            self.status = PackNFT.Status.Opened
177            emit Opened(id: id)
178        }
179
180        init(commitHash: String, issuer: Address, nftCount: UInt16?, lockTime: UFix64?) {
181            self.commitHash = commitHash
182            self.issuer = issuer
183            self.status = PackNFT.Status.Sealed
184            self.salt = nil
185            self.nftCount = nftCount
186            self.lockTime = lockTime
187        }
188    }
189
190    access(all) struct Metadata {
191        access(all) let title: String
192        access(all) let description: String
193        access(all) let creator: String
194        access(all) let asset: String
195        access(all) let assetMimeType: String
196        access(all) let assetHash: String
197        access(all) let artwork: String
198        access(all) let artworkMimeType: String
199        access(all) let artworkHash: String
200        access(all) let artworkAlternate: String?
201        access(all) let artworkAlternateMimeType: String?
202        access(all) let artworkAlternateHash: String?
203        access(all) let artworkOpened: String?
204        access(all) let artworkOpenedMimeType: String?
205        access(all) let artworkOpenedHash: String?
206        access(all) let thumbnail: String
207        access(all) let thumbnailMimeType: String
208        access(all) let thumbnailOpened: String?
209        access(all) let thumbnailOpenedMimeType: String?
210        access(all) let termsUrl: String
211        access(all) let externalUrl: String?
212        access(all) let rarity: String?
213        access(all) let credits: String?
214
215        access(all) fun toDict(): {String: AnyStruct?} {
216            let rawMetadata: {String: AnyStruct?} = {}
217            rawMetadata.insert(key: "title", self.title)
218            rawMetadata.insert(key: "description", self.description)
219            rawMetadata.insert(key: "creator", self.creator)
220            if(self.asset.length == 0){
221                rawMetadata.insert(key: "asset", nil)
222                rawMetadata.insert(key: "assetMimeType", nil)
223                rawMetadata.insert(key: "assetHash", nil)
224            }else{
225                rawMetadata.insert(key: "asset", self.asset)
226                rawMetadata.insert(key: "assetMimeType", self.assetMimeType)
227                rawMetadata.insert(key: "assetHash", self.assetHash)
228            }
229            rawMetadata.insert(key: "artwork", self.artwork)
230            rawMetadata.insert(key: "artworkMimeType", self.artworkMimeType)
231            rawMetadata.insert(key: "artworkHash", self.artworkHash)
232            rawMetadata.insert(key: "artworkAlternate", self.artworkAlternate)
233            rawMetadata.insert(key: "artworkAlternateMimeType", self.artworkAlternateMimeType)
234            rawMetadata.insert(key: "artworkAlternateHash", self.artworkAlternateHash)
235            rawMetadata.insert(key: "artworkOpened", self.artworkOpened)
236            rawMetadata.insert(key: "artworkOpenedMimeType", self.artworkOpenedMimeType)
237            rawMetadata.insert(key: "artworkOpenedHash", self.artworkOpenedHash)
238            rawMetadata.insert(key: "thumbnail", self.thumbnail)
239            rawMetadata.insert(key: "thumbnailMimeType", self.thumbnailMimeType)
240            rawMetadata.insert(key: "thumbnailOpened", self.thumbnailOpened)
241            rawMetadata.insert(key: "thumbnailOpenedMimeType", self.thumbnailOpenedMimeType)
242            rawMetadata.insert(key: "termsUrl", self.termsUrl)
243            rawMetadata.insert(key: "externalUrl", self.externalUrl)
244            rawMetadata.insert(key: "rarity", self.rarity)
245            rawMetadata.insert(key: "credits", self.credits)
246
247            return rawMetadata
248        }
249
250        access(all) fun toStringDict(): {String: String?} {
251            let rawMetadata: {String: String?} = {}
252            rawMetadata.insert(key: "title", self.title)
253            rawMetadata.insert(key: "description", self.description)
254            rawMetadata.insert(key: "creator", self.creator)
255            if(self.asset.length == 0){
256                rawMetadata.insert(key: "asset", nil)
257                rawMetadata.insert(key: "assetMimeType", nil)
258                rawMetadata.insert(key: "assetHash", nil)
259            }else{
260                rawMetadata.insert(key: "asset", self.asset)
261                rawMetadata.insert(key: "assetMimeType", self.assetMimeType)
262                rawMetadata.insert(key: "assetHash", self.assetHash)
263            }
264            rawMetadata.insert(key: "artwork", self.artwork)
265            rawMetadata.insert(key: "artworkMimeType", self.artworkMimeType)
266            rawMetadata.insert(key: "artworkHash", self.artworkHash)
267            rawMetadata.insert(key: "artworkAlternate", self.artworkAlternate)
268            rawMetadata.insert(key: "artworkAlternateMimeType", self.artworkAlternateMimeType)
269            rawMetadata.insert(key: "artworkAlternateHash", self.artworkAlternateHash)
270            rawMetadata.insert(key: "artworkOpened", self.artworkOpened)
271            rawMetadata.insert(key: "artworkOpenedMimeType", self.artworkOpenedMimeType)
272            rawMetadata.insert(key: "artworkOpenedHash", self.artworkOpenedHash)
273            rawMetadata.insert(key: "thumbnail", self.thumbnail)
274            rawMetadata.insert(key: "thumbnailMimeType", self.thumbnailMimeType)
275            rawMetadata.insert(key: "thumbnailOpened", self.thumbnailOpened)
276            rawMetadata.insert(key: "thumbnailOpenedMimeType", self.thumbnailOpenedMimeType)
277            rawMetadata.insert(key: "termsUrl", self.termsUrl)
278            rawMetadata.insert(key: "externalUrl", self.externalUrl)
279            rawMetadata.insert(key: "rarity", self.rarity)
280            rawMetadata.insert(key: "credits", self.credits)
281
282            return rawMetadata
283        }
284
285        access(all) fun patchedForOpened(): Metadata {
286            return Metadata(
287                title: PackNFT.metadataOpenedWarning.concat(self.title),
288                description: PackNFT.metadataOpenedWarning.concat(self.description),
289                creator: self.creator,
290                asset: self.asset,
291                assetMimeType: self.assetMimeType,
292                assetHash: self.assetHash,
293                artwork: self.artworkOpened ?? self.artwork,
294                artworkMimeType: self.artworkOpenedMimeType ?? self.artworkMimeType,
295                artworkHash: self.artworkOpenedHash ?? self.artworkHash,
296                artworkAlternate: self.artworkAlternate,
297                artworkAlternateMimeType: self.artworkAlternateMimeType,
298                artworkAlternateHash: self.artworkAlternateHash,
299                artworkOpened: self.artworkOpened,
300                artworkOpenedMimeType: self.artworkOpenedMimeType,
301                artworkOpenedHash: self.artworkOpenedHash,
302                thumbnail: self.thumbnailOpened ?? self.thumbnail,
303                thumbnailMimeType: self.thumbnailOpenedMimeType ?? self.thumbnailMimeType,
304                thumbnailOpened: self.thumbnailOpened,
305                thumbnailOpenedMimeType: self.thumbnailOpenedMimeType,
306                termsUrl: self.termsUrl,
307                externalUrl: self.externalUrl,
308                rarity: self.rarity,
309                credits: self.credits
310            )
311        }
312
313
314        init(
315            title: String,
316            description: String,
317            creator: String,
318            asset: String,
319            assetMimeType: String,
320            assetHash: String,
321            artwork: String,
322            artworkMimeType: String,
323            artworkHash: String,
324            artworkAlternate: String?,
325            artworkAlternateMimeType: String?,
326            artworkAlternateHash: String?,
327            artworkOpened: String?,
328            artworkOpenedMimeType: String?,
329            artworkOpenedHash: String?,
330            thumbnail: String,
331            thumbnailMimeType: String,
332            thumbnailOpened: String?,
333            thumbnailOpenedMimeType: String?,
334            termsUrl: String,
335            externalUrl: String?,
336            rarity: String?,
337            credits: String?
338        ) {
339
340            self.title = title
341            self.description = description
342            self.creator = creator
343            self.asset = asset
344            self.assetMimeType = assetMimeType
345            self.assetHash = assetHash
346            self.artwork = artwork
347            self.artworkMimeType = artworkMimeType
348            self.artworkHash = artworkHash
349            self.artworkAlternate = artworkAlternate
350            self.artworkAlternateMimeType = artworkAlternateMimeType
351            self.artworkAlternateHash = artworkAlternateHash
352            self.artworkOpened = artworkOpened
353            self.artworkOpenedMimeType = artworkOpenedMimeType
354            self.artworkOpenedHash = artworkOpenedHash
355            self.thumbnail = thumbnail
356            self.thumbnailMimeType = thumbnailMimeType
357            self.thumbnailOpened = thumbnailOpened
358            self.thumbnailOpenedMimeType = thumbnailOpenedMimeType
359            self.termsUrl = termsUrl
360            self.externalUrl = externalUrl
361            self.credits = credits
362            self.rarity = rarity
363        }
364    }
365
366
367    access(all) struct CollectionMetadata {
368        access(all) let name: String
369        access(all) let description: String
370        access(all) let URL: String
371        access(all) let media: String
372        access(all) let mediaMimeType: String
373        access(all) let mediaBanner: String?
374        access(all) let mediaBannerMimeType: String?
375        access(all) let socials: {String:String}
376
377
378        access(all) fun toDict(): {String: AnyStruct?} {
379            let rawMetadata: {String: AnyStruct?} = {}
380
381            rawMetadata.insert(key: "name", self.name)
382            rawMetadata.insert(key: "description", self.description)
383            rawMetadata.insert(key: "URL", self.URL)
384            rawMetadata.insert(key: "media", self.media)
385            rawMetadata.insert(key: "mediaMimeType", self.mediaMimeType)
386            rawMetadata.insert(key: "mediaBanner", self.mediaBanner)
387            rawMetadata.insert(key: "mediaBannerMimeType", self.mediaBanner)
388            rawMetadata.insert(key: "socials", self.socials)
389
390            return rawMetadata
391        }
392
393        init(
394            name: String,
395            description: String,
396            URL: String,
397            media: String,
398            mediaMimeType: String,
399            mediaBanner: String?,
400            mediaBannerMimeType: String?,
401            socials: {String:String}?,
402        ) {
403            self.name = name
404            self.description = description
405            self.URL = URL
406            self.media = media
407            self.mediaMimeType = mediaMimeType
408            self.mediaBanner = mediaBanner
409            self.mediaBannerMimeType = mediaBannerMimeType
410            self.socials = socials ?? {}
411        }
412    }
413
414    access(all) resource interface IPackNFTToken {
415        access(all) let id: UInt64
416        access(all) let edition: UInt32
417        access(all) let commitHash: String
418        access(all) let issuer: Address
419        access(all) let nftCount: UInt16?
420        access(all) let lockTime: UFix64?
421    }
422
423    access(all) entitlement OwnerReveal
424    access(all) entitlement OwnerOpen
425    access(all) entitlement Owner
426
427
428    access(all) resource interface IPackNFTOwnerOperator {
429        access(OwnerReveal /*| Owner*/)  fun reveal(openRequest: Bool)
430        access(OwnerOpen /*| Owner*/)  fun open()
431     }
432
433    access(all) resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver, IPackNFTToken, IPackNFTOwnerOperator {
434        access(all) event ResourceDestroyed(id: UInt64 = self.id)
435
436        access(all) let id: UInt64
437        access(all) let distId: UInt64
438        access(all) let edition: UInt32
439        access(all) let commitHash: String
440        access(all) let issuer: Address
441        access(all) let nftCount: UInt16?
442        access(all) let lockTime: UFix64?
443
444        access(self) let additionalInfo: {String: String}
445        access(all) let mintedBlock: UInt64
446        access(all) let mintedTime: UFix64
447
448        access(OwnerReveal  /*| Owner*/) fun reveal(openRequest: Bool){
449            PackNFT.revealRequest(id: self.id, openRequest: openRequest)
450        }
451
452        access(OwnerOpen  /*| Owner*/) fun open(){
453            pre {
454                (self.lockTime == nil) || (getCurrentBlock().timestamp > self.lockTime!): "Pack is locked until ".concat(self.lockTime!.toString())
455            }
456            PackNFT.openRequest(id: self.id)
457        }
458
459        access(all) view fun getAdditionalInfo(): {String: String} {
460            return self.additionalInfo
461        }
462
463        access(all) view fun totalEditions(): UInt32 {
464            return PackNFT.itemEditions[self.distId] ?? UInt32(0)
465        }
466
467        access(all) view fun getViews(): [Type] {
468            return [
469                Type<Metadata>(),
470                Type<MetadataViews.Display>(),
471                Type<MetadataViews.Editions>(),
472                Type<MetadataViews.ExternalURL>(),
473                Type<MetadataViews.NFTCollectionData>(),
474                Type<MetadataViews.NFTCollectionDisplay>(),
475                Type<MetadataViews.Serial>(),
476                Type<MetadataViews.Traits>()
477            ]
478        }
479
480        access(all) fun resolveView(_ view: Type): AnyStruct? {
481            switch view {
482                case Type<Metadata>():
483                    return self.metadata()
484                case Type<MetadataViews.Display>():
485                    return MetadataViews.Display(
486                        name: self.metadata().title,
487                        description: self.metadata().description,
488                        thumbnail: MetadataViews.HTTPFile(
489                            url: self.metadata().thumbnail
490                        )
491                    )
492                case Type<MetadataViews.Serial>():
493                    return MetadataViews.Serial(
494                        UInt64(self.edition)
495                    )
496                case Type<MetadataViews.Editions>():
497                    let name = PackNFT.defaultCollectionMetadata!.name
498                    let editionInfo = MetadataViews.Edition(name: name, number: UInt64(self.edition), max: nil)
499                    let editionList: [MetadataViews.Edition] = [editionInfo]
500                    return MetadataViews.Editions(
501                        editionList
502                    )
503                case Type<MetadataViews.ExternalURL>():
504                    return MetadataViews.ExternalURL(
505                        self.metadata().externalUrl ?? "https://www.tunegonft.com/view-pack-collectible/".concat(self.uuid.toString())
506                    )
507                case Type<MetadataViews.NFTCollectionData>():
508                    return PackNFT.resolveContractView(resourceType: Type<@PackNFT.NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
509                case Type<MetadataViews.NFTCollectionDisplay>():
510                    return PackNFT.resolveContractView(resourceType: Type<@PackNFT.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
511                case Type<MetadataViews.Traits>():
512                    let excludedTraits = ["mintedTime"]
513                    let dict = self.metadataDict()
514                    dict.forEachKey(fun (key: String): Bool {
515                        if (dict[key] == nil) {
516                            dict.remove(key: key)
517                        }
518                        return false
519                    })
520                    let traitsView = MetadataViews.dictToTraits(dict: dict, excludedNames: excludedTraits)
521
522                    // mintedTime is a unix timestamp, we should mark it with a displayType so platforms know how to show it.
523                    let mintedTimeTrait = MetadataViews.Trait(name: "mintedTime", value: self.mintedTime!, displayType: "Date", rarity: nil)
524                    traitsView.addTrait(mintedTimeTrait)
525
526                    return traitsView
527            }
528
529            return nil
530        }
531
532        access(all) fun _metadata(): Metadata  {
533            let metadata = PackNFT.getMetadata(distId: self.distId, edition: self.edition)
534            if metadata != nil {
535                return metadata!
536            }
537            let fullId = self.distId.toString().concat(":").concat(self.edition.toString())
538            panic("Metadata not found for collectible ".concat(fullId))
539        }
540
541        access(all) fun metadata(): Metadata {
542            let metadata = self._metadata()
543            let p = PackNFT.borrowPackRepresentation(id: self.id) ?? panic ("Pack representation not found")
544            if p.status.rawValue == PackNFT.Status.Opened.rawValue {
545                return metadata.patchedForOpened()
546            }
547            return metadata
548        }
549
550        access(all) fun metadataDict(): {String: AnyStruct?} {
551            let dict = self.metadata().toDict()
552            let collectionDict = PackNFT.defaultCollectionMetadata?.toDict()
553            if (collectionDict != nil) {
554                collectionDict!.forEachKey(fun (key: String): Bool {
555                    dict.insert(key: "collection_".concat(key), collectionDict![key])
556                    return false
557                })
558            }
559
560            dict.insert(key: "mintedBlock", self.mintedBlock)
561            dict.insert(key: "mintedTime", self.mintedTime)
562
563            return dict
564        }
565
566        init(
567            id: UInt64,
568            distId: UInt64,
569            edition: UInt32,
570            additionalInfo: {String: String}?,
571            commitHash: String,
572            issuer: Address,
573            nftCount: UInt16?,
574            lockTime: UFix64?
575        ) {
576            self.id = id
577            self.distId = distId
578            self.edition = edition
579            self.additionalInfo = additionalInfo ?? {}
580            self.commitHash = commitHash
581            self.issuer = issuer
582            self.nftCount = nftCount
583            self.lockTime = lockTime
584            let currentBlock = getCurrentBlock()
585            self.mintedBlock = currentBlock.height
586            self.mintedTime = currentBlock.timestamp
587
588            // asserts metadata exists for distribution / edition
589            self._metadata()
590        }
591
592        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
593            return <-PackNFT.createEmptyCollection(nftType: Type<@PackNFT.NFT>())
594        }
595
596    }
597    access(all) resource interface IPackNFTCollectionPublic: NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver {
598        access(all) fun deposit(token: @{NonFungibleToken.NFT})
599        access(all) view fun getIDs(): [UInt64]
600        access(all) view fun getLength(): Int
601        access(all) view fun getSupportedNFTTypes(): {Type: Bool}
602        access(all) view fun isSupportedNFTType(type: Type): Bool
603        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
604        access(all) view fun borrowPackNFT(_ id: UInt64): &NFT? {
605            // If the result isn't nil, the id of the returned reference
606            // should be the same as the argument to the function
607            post {
608                (result == nil) || ((result as &NFT?)!.id == id):
609                    "Cannot borrow PackNFT reference: The ID of the returned reference is incorrect"
610            }
611        }
612        access(OwnerReveal, OwnerOpen) view fun borrowPackNFTOwned(_ id: UInt64): &NFT? {
613            // If the result isn't nil, the id of the returned reference
614            // should be the same as the argument to the function
615            post {
616                (result == nil) || ((result as &NFT?)!.id == id):
617                    "Cannot borrow PackNFT reference: The ID of the returned reference is incorrect"
618            }
619        }
620    }
621
622    access(all) resource interface IPackNFTCollectionPrivate { }
623
624    access(all) resource Collection:
625        NonFungibleToken.Collection,
626        NonFungibleToken.Provider,
627        NonFungibleToken.Receiver,
628        NonFungibleToken.CollectionPublic,
629        ViewResolver.ResolverCollection,
630        IPackNFTCollectionPublic,
631        IPackNFTCollectionPrivate
632    {
633        // dictionary of NFT conforming tokens
634        // NFT is a resource type with an `UInt64` ID field
635        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
636
637        init () {
638            self.ownedNFTs <- {}
639        }
640
641        /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
642        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
643            let supportedTypes: {Type: Bool} = {}
644            supportedTypes[Type<@PackNFT.NFT>()] = true
645            return supportedTypes
646        }
647        /// Returns whether or not the given type is accepted by the collection
648        /// A collection that can accept any type should just return true by default
649        access(all) view fun isSupportedNFTType(type: Type): Bool {
650           if type == Type<@PackNFT.NFT>() {
651            return true
652           }
653           return false
654        }
655
656        /// withdraw removes an NFT from the collection and moves it to the caller
657        access(NonFungibleToken.Withdraw)
658        fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
659            let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("Missing NFT")
660            let nft <- token  as! @PackNFT.NFT
661            if(!PackNFT.loadCollectionManager().checkWithdraw(collectionId: self.uuid, owner: self.owner, distId: nft.distId, id: nft.id)){
662                panic("Withdrawal of NFT [".concat(nft.id.toString()).concat("] is currently not permitted."))
663            }
664
665            emit Withdraw(id: nft.id, from: self.owner?.address)
666            return <- nft
667        }
668
669        /// deposit takes a NFT and adds it to the collections dictionary
670        /// and adds the ID to the id array
671        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
672            let token <- token as! @PackNFT.NFT
673
674            let id: UInt64 = token.id
675
676            // add the new token to the dictionary which removes the old one
677            let oldToken <- self.ownedNFTs[id] <- token
678            emit Deposit(id: id, to: self.owner?.address)
679
680            destroy oldToken
681        }
682
683        // getIDs returns an array of the IDs that are in the collection
684        access(all) view fun getIDs(): [UInt64] {
685            return self.ownedNFTs.keys
686        }
687
688        // Gets the amount of NFTs stored in the collection
689        access(all) view fun getLength(): Int {
690            return self.ownedNFTs.keys.length
691        }
692
693        // borrowNFT gets a reference to an NFT in the collection
694        // so that the caller can read its metadata and call its methods
695        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
696            return &self.ownedNFTs[id] as &{NonFungibleToken.NFT}?
697        }
698
699        access(all) view fun borrowPackNFT(_ id: UInt64): &NFT? {
700            if self.ownedNFTs[id] != nil {
701                return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}? as! &NFT?)
702            } else {
703                return nil
704            }
705        }
706
707        access(OwnerReveal, OwnerOpen) view fun borrowPackNFTOwned(_ id: UInt64): auth(OwnerReveal, OwnerOpen) &NFT? {
708            if self.ownedNFTs[id] != nil {
709                return (&self.ownedNFTs[id] as auth(OwnerReveal, OwnerOpen) &{NonFungibleToken.NFT}? as! auth(OwnerReveal, OwnerOpen) &NFT?)
710            } else {
711                return nil
712            }
713        }
714
715        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
716            if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
717                return nft as &{ViewResolver.Resolver}
718            }
719            return nil
720        }
721
722        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
723            return <-create PackNFT.Collection()
724        }
725    }
726
727
728    // -----------------------------------------------------------------------
729    // PackNFT contract-level function definitions
730    // -----------------------------------------------------------------------
731
732    access(contract) fun revealRequest(id: UInt64, openRequest: Bool ) {
733        let p = PackNFT.borrowPackRepresentation(id: id) ?? panic ("No such pack")
734        assert(p.status.rawValue == PackNFT.Status.Sealed.rawValue, message: "Pack status must be Sealed for reveal request")
735        emit RevealRequest(id: id, openRequest: openRequest)
736    }
737
738    access(contract) fun openRequest(id: UInt64) {
739        let p = PackNFT.borrowPackRepresentation(id: id) ?? panic ("No such pack")
740        assert(p.status.rawValue == PackNFT.Status.Revealed.rawValue, message: "Pack status must be Revealed for open request")
741        emit OpenRequest(id: id)
742    }
743
744    access(all) fun publicReveal(id: UInt64, nfts: [&{NonFungibleToken.NFT}], salt: String) {
745        let p = PackNFT.borrowPackRepresentation(id: id) ?? panic ("No such pack")
746        p.reveal(id: id, nfts: nfts, salt: salt)
747    }
748
749    access(all) fun borrowPackRepresentation(id: UInt64):  &Pack? {
750        return &self.packs[id] as &Pack?
751    }
752
753    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
754        assert(nftType == Type<@PackNFT.NFT>(), message: "I don't know how to create ".concat(nftType.identifier))
755        return <- create Collection()
756    }
757
758    access(all) view fun getContractViews(resourceType: Type?): [Type] {
759        return [
760            Type<MetadataViews.NFTCollectionData>(),
761            Type<MetadataViews.NFTCollectionDisplay>()
762        ]
763    }
764    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
765        switch viewType {
766                case Type<MetadataViews.NFTCollectionData>():
767                    return MetadataViews.NFTCollectionData(
768                        storagePath: PackNFT.CollectionStoragePath,
769                        publicPath: PackNFT.CollectionPublicPath,
770                        publicCollection: Type<&PackNFT.Collection>(),
771                        publicLinkedType: PackNFT.CollectionPublicType,
772                        createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
773                            return <-PackNFT.createEmptyCollection(nftType: Type<@PackNFT.NFT>())
774                        })
775                    )
776                case Type<MetadataViews.NFTCollectionDisplay>():
777                    let collectionMetadata = PackNFT.defaultCollectionMetadata!
778                    let media = MetadataViews.Media(
779                        file: MetadataViews.HTTPFile(
780                            url: collectionMetadata.media
781                        ),
782                        mediaType: collectionMetadata.mediaMimeType
783                    )
784                    let mediaBanner = collectionMetadata.mediaBanner != nil ?
785                        MetadataViews.Media(
786                            file: MetadataViews.HTTPFile(
787                                url: collectionMetadata.mediaBanner!
788                            ),
789                            mediaType: collectionMetadata.mediaBannerMimeType!
790                        )
791                        : media
792                    let socials: {String:MetadataViews.ExternalURL} = {}
793                    collectionMetadata.socials.forEachKey(fun (key: String): Bool {
794                        socials.insert(key: key,MetadataViews.ExternalURL(collectionMetadata.socials[key]!))
795                        return false
796                    })
797                    return MetadataViews.NFTCollectionDisplay(
798                        name: collectionMetadata.name,
799                        description: collectionMetadata.description,
800                        externalURL: MetadataViews.ExternalURL(collectionMetadata.URL),
801                        squareImage: media,
802                        bannerImage: mediaBanner,
803                        socials: socials
804                    )
805        }
806        return nil
807    }
808
809    access(all) fun getMetadata(distId: UInt64, edition: UInt32?): Metadata? {
810        if edition != nil {
811            let fullId = distId.toString().concat(":").concat(edition!.toString())
812            let editionMetadata = PackNFT.itemMetadata[fullId]
813            if editionMetadata != nil { return editionMetadata }
814        }
815        return PackNFT.itemMetadata[distId.toString()]
816    }
817
818    access(all) resource CollectionManager {
819        access(contract) var withheldDefault: Bool
820        access(contract) let privilegedCollections: [UInt64]
821        access(contract) let privilegedCollectionOwners: [String]
822        access(contract) let withheldDistIds: [UInt64]
823        access(contract) let withheldIds: [UInt64]
824        access(contract) let whitelistedDistIds: [UInt64]
825        access(contract) let whitelistedIds: [UInt64]
826
827
828        init() {
829           self.withheldDefault = false
830           self.privilegedCollections = []
831           self.privilegedCollectionOwners = []
832           self.withheldDistIds = []
833           self.withheldIds = []
834           self.whitelistedDistIds = []
835           self.whitelistedIds = []
836        }
837
838        access(all) fun checkWithdraw(collectionId: UInt64, owner: &Account?, distId: UInt64, id: UInt64): Bool {
839            if(self.privilegedCollections.contains(collectionId)){ return true }
840            if(owner != nil && self.privilegedCollectionOwners.contains(owner!.address.toString())){ return true }
841            if(self.withheldIds.contains(id)){ return false }
842            if(self.whitelistedIds.contains(id)){ return true }
843            if(self.withheldDistIds.contains(distId)){ return false }
844            if(self.whitelistedDistIds.contains(distId)){ return true }
845            return !self.withheldDefault
846        }
847
848        access(all) fun setWithheldDefault(_ value: Bool) {
849            self.withheldDefault = value;
850        }
851
852        access(all) fun addPrivilegedCollection(_ collectionId: UInt64) {
853          if(!self.privilegedCollections.contains(collectionId)){
854            self.privilegedCollections.append(collectionId)
855          }
856        }
857        access(all) fun removePrivilegedCollection(_ collectionId: UInt64) {
858          let index = self.privilegedCollections.firstIndex(of: collectionId)
859          if(index != nil){
860            self.privilegedCollections.remove(at: index!)
861          }
862        }
863
864        access(all) fun addPrivilegedCollectionOwner(_ addrString: String) {
865          if(!self.privilegedCollectionOwners.contains(addrString)){
866            self.privilegedCollectionOwners.append(addrString)
867          }
868        }
869        access(all) fun removePrivilegedCollectionOwner(_ addrString: String) {
870          let index = self.privilegedCollectionOwners.firstIndex(of: addrString)
871          if(index != nil){
872            self.privilegedCollectionOwners.remove(at: index!)
873          }
874        }
875
876        access(all) fun addWithheldDistId(_ distId: UInt64) {
877          if(!self.withheldDistIds.contains(distId)){
878            self.withheldDistIds.append(distId)
879          }
880        }
881        access(all) fun removeWithheldDistId(_ distId: UInt64) {
882          let index = self.withheldDistIds.firstIndex(of: distId)
883          if(index != nil){
884            self.withheldDistIds.remove(at: index!)
885          }
886        }
887
888        access(all) fun addWithheldId(_ id: UInt64) {
889          if(!self.withheldIds.contains(id)){
890            self.withheldIds.append(id)
891          }
892        }
893        access(all) fun removeWithheldId(_ id: UInt64) {
894          let index = self.withheldIds.firstIndex(of: id)
895          if(index != nil){
896            self.withheldIds.remove(at: index!)
897          }
898        }
899
900
901        access(all) fun addWhitelistedDistId(_ distId: UInt64) {
902          if(!self.whitelistedDistIds.contains(distId)){
903            self.whitelistedDistIds.append(distId)
904          }
905        }
906        access(all) fun removeWhitelistedDistId(_ distId: UInt64) {
907          let index = self.whitelistedDistIds.firstIndex(of: distId)
908          if(index != nil){
909            self.whitelistedDistIds.remove(at: index!)
910          }
911        }
912
913        access(all) fun addWhitelistedId(_ id: UInt64) {
914          if(!self.whitelistedIds.contains(id)){
915            self.whitelistedIds.append(id)
916          }
917        }
918        access(all) fun removeWhitelistedId(_ id: UInt64) {
919          let index = self.whitelistedIds.firstIndex(of: id)
920          if(index != nil){
921            self.whitelistedIds.remove(at: index!)
922          }
923        }
924
925    }
926
927    access(account) fun loadCollectionManager(): &CollectionManager {
928        if let existing = self.account.storage.borrow<&CollectionManager>(from: /storage/packCollectionManager) {
929            return existing
930        }
931        let res <- create CollectionManager()
932        self.account.storage.save(<-res, to: /storage/packCollectionManager)
933
934        return self.account.storage.borrow<&CollectionManager>(from: /storage/packCollectionManager)!
935    }
936
937    init() {
938        self.totalSupply = 0
939        self.itemEditions = {}
940        self.packs <- {}
941        self.CollectionStoragePath = /storage/tunegoPack
942        self.CollectionPublicPath = /public/tunegoPack
943        self.OperatorStoragePath = /storage/tunegoPackOperator
944        self.defaultCollectionMetadata = nil
945        self.version = "1.0"
946
947        self.itemMetadata = {}
948        self.itemCollectionMetadata = {}
949        self.metadataOpenedWarning = "WARNING this pack has already been opened! \n"
950
951        self.CollectionPublicType = Type<&{NonFungibleToken.CollectionPublic, ViewResolver.ResolverCollection, IPackNFTCollectionPublic}>()
952
953        // Create a collection to receive Pack NFTs
954        let collection <- create Collection()
955        self.account.storage.save(<-collection, to: self.CollectionStoragePath)
956        self.account.capabilities.publish(
957            self.account.capabilities.storage.issue<&{IPackNFTCollectionPublic}>(self.CollectionStoragePath),
958            at: self.CollectionPublicPath
959        )
960
961        // Create a operator to share mint capability with proxy
962        self.account.storage.save(<-create PackNFTOperator(), to: self.OperatorStoragePath)
963    }
964
965}