Smart Contract
PackNFT
A.c6945445cdbefec9.PackNFT
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}