Smart Contract

AeraPack

A.30cf5dcf6ea8d379.AeraPack

Deployed

1d ago
Feb 26, 2026, 09:43:51 PM UTC

Dependents

0 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import FungibleToken from 0xf233dcee88fe0abe
3import MetadataViews from 0x1d7e57aa55817448
4import Crypto
5import Clock from 0x30cf5dcf6ea8d379
6import Debug from 0x30cf5dcf6ea8d379
7import FLOAT from 0x2d4c3caffbeab845
8import AeraPackExtraData from 0x30cf5dcf6ea8d379
9
10
11pub contract AeraPack: NonFungibleToken {
12	// Events
13	pub event ContractInitialized()
14	pub event Withdraw(id: UInt64, from: Address?)
15	pub event Deposit(id: UInt64, to: Address?)
16	pub event Minted(id: UInt64, typeId:UInt64)
17
18	pub event Requeued(packId: UInt64, address:Address)
19
20	pub event Opened(packId: UInt64, address:Address, packTypeId:UInt64)
21	pub event Fulfilled(packId:UInt64, address:Address)
22	pub event PackReveal(packId:UInt64, address:Address, packTypeId:UInt64, rewardId:UInt64, rewardType:String, rewardFields:{String:String}, nftPerPack: Int, packTier: String)
23
24	pub event Purchased(packId: UInt64, address: Address, amount:UFix64, packTypeId:UInt64)
25	pub event MetadataRegistered(typeId:UInt64)
26	pub event FulfilledError(packId:UInt64, address:Address?, reason:String)
27
28	pub let PackMetadataStoragePath: StoragePath
29
30	pub let CollectionStoragePath: StoragePath
31	pub let CollectionPublicPath: PublicPath
32
33	pub let OpenedCollectionPublicPath: PublicPath
34	pub let OpenedCollectionStoragePath: StoragePath
35
36
37	pub let DLQCollectionPublicPath: PublicPath
38	pub let DLQCollectionStoragePath: StoragePath
39
40	pub var totalSupply: UInt64
41
42	access(contract) let packMetadata: {UInt64: Metadata}
43
44	pub struct PackRevealData {
45
46		pub let data: {String:String}
47
48		init(_ data: {String:String}) {
49			self.data=data
50		}
51	}
52
53	pub struct Metadata {
54		pub let name: String
55		pub let description: String
56
57		pub let thumbnailHash: String?
58		pub let thumbnailUrl:String?
59
60		pub let wallet: Capability<&{FungibleToken.Receiver}>
61		pub let walletType: Type
62		pub let price: UFix64
63
64		pub let buyTime:UFix64
65
66		pub let openTime:UFix64
67		pub let whiteListTime:UFix64?
68
69		pub let floatEventId: UInt64?
70
71		pub let storageRequirement: UInt64
72
73		access(contract) let providerCap: Capability<&{NonFungibleToken.Provider, MetadataViews.ResolverCollection}> 
74
75		access(contract) let royaltyCap: Capability<&{FungibleToken.Receiver}>?
76		access(contract) let royaltyCut: UFix64
77
78		pub let requiresReservation: Bool
79
80		init(name: String, description: String, thumbnailUrl: String?,thumbnailHash: String?, wallet: Capability<&{FungibleToken.Receiver}>, price: UFix64, buyTime:UFix64, openTime:UFix64, walletType:Type, providerCap: Capability<&{NonFungibleToken.Provider, MetadataViews.ResolverCollection}>, requiresReservation:Bool, royaltyCut: UFix64, royaltyWallet: Capability<&{FungibleToken.Receiver}>?, floatEventId:UInt64?, whiteListTime: UFix64?, storageRequirement: UInt64) {
81			self.name = name
82			self.description = description
83			self.thumbnailUrl = thumbnailUrl
84			self.thumbnailHash = thumbnailHash
85			self.wallet=wallet
86			self.walletType=walletType
87			self.price =price
88			self.buyTime=buyTime
89			self.openTime=openTime
90			self.providerCap=providerCap
91
92			//If this pack has royalties then they can be added here later. For the current implementations royalties appear to be handled offchain. 
93			self.royaltyCap=royaltyWallet
94			self.royaltyCut=royaltyCut
95
96			self.floatEventId=floatEventId
97			self.whiteListTime=whiteListTime
98
99			self.storageRequirement= storageRequirement
100
101			self.requiresReservation=requiresReservation
102		}
103
104		pub fun getThumbnail() : AnyStruct{MetadataViews.File} {
105			if let hash = self.thumbnailHash {
106				return MetadataViews.IPFSFile(cid: hash, path: nil)
107			}
108			return MetadataViews.HTTPFile(url:self.thumbnailUrl!)
109		}
110
111		pub fun canBeOpened() : Bool {
112			return self.openTime < Clock.time()
113		}
114	}
115
116	access(account) fun registerMetadata(typeId: UInt64, metadata: Metadata) {
117		emit MetadataRegistered(typeId:typeId)
118		self.packMetadata[typeId]= metadata
119	}
120
121	pub fun getMetadata(typeId: UInt64): Metadata? {
122		return self.packMetadata[typeId]
123	}
124
125	pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
126		// The token's ID
127		pub let id: UInt64
128
129		// The token's typeId
130		access(self) var typeId: UInt64
131
132		//this is added to the NFT when it is opened
133		access(self) var openedBy: Capability<&{NonFungibleToken.Receiver}>?
134
135		access(account) let hash: String
136
137		// init
138		//
139		init(typeId: UInt64, hash:String) {
140			self.id = self.uuid
141			self.typeId = typeId
142			self.openedBy=nil
143			self.hash=hash
144		}
145
146		pub fun getOpenedBy() : Capability<&{NonFungibleToken.Receiver}> {
147			if self.openedBy== nil {
148				panic("Pack is not opened")
149			}
150			return self.openedBy!
151		}
152
153		pub fun getHash() : String{
154			return self.hash
155		}
156
157		access(contract) fun setTypeId(_ id: UInt64) {
158			self.typeId=id
159		}
160
161		access(contract) fun resetOpenedBy() : Address {
162			if self.openedBy==nil {
163				panic("Pack is not opened")
164			}
165  		let cap = self.openedBy!
166
167			self.openedBy=nil
168			return cap.address
169		}
170
171		access(contract) fun setOpenedBy(_ cap:Capability<&{NonFungibleToken.Receiver}>) {
172			if self.openedBy!=nil {
173				panic("Pack has already been opened")
174			}
175			self.openedBy=cap
176		}
177
178		pub fun getTypeID() :UInt64 {
179			return self.typeId
180		}
181
182		pub fun getMetadata(): Metadata {
183			return AeraPack.getMetadata(typeId: self.typeId)!
184		}
185
186		pub fun getViews(): [Type] {
187			return [
188			Type<MetadataViews.Display>(), 
189			Type<MetadataViews.NFTCollectionData>(),
190			Type<MetadataViews.Traits>(),
191			Type<Metadata>(),
192			Type<String>()
193			]
194		}
195
196		pub fun resolveView(_ view: Type): AnyStruct? {
197			let metadata = self.getMetadata()
198			switch view {
199			case Type<MetadataViews.Display>():
200				return MetadataViews.Display(
201					name: metadata.name,
202					description: metadata.description,
203					thumbnail: metadata.getThumbnail()
204				)
205
206			case Type<MetadataViews.NFTCollectionData>():
207				return MetadataViews.NFTCollectionData(
208					storagePath: AeraPack.CollectionStoragePath, 
209					publicPath: AeraPack.CollectionPublicPath, 
210					providerPath: /private/AeraPackCollection, 
211					publicCollection: Type<&AeraPack.Collection{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>(),
212					publicLinkedType: Type<&AeraPack.Collection{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>(), 
213					providerLinkedType: Type<&AeraPack.Collection{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>(), 
214					createEmptyCollectionFunction: fun(): @NonFungibleToken.Collection {return <- AeraPack.createEmptyCollection()}
215			)
216
217			case Type<MetadataViews.Traits>(): 
218				return MetadataViews.Traits([
219					MetadataViews.Trait(name: "pack_type_id", value: self.typeId, displayType: "Number", rarity: nil)
220				])
221
222			case Type<String>():
223				return metadata.name
224
225			case Type<AeraPack.Metadata>(): 
226				return metadata
227			}
228
229			return nil
230		}
231
232	}
233
234	pub resource interface CollectionPublic {
235		pub fun deposit(token: @NonFungibleToken.NFT)
236		pub fun getIDs(): [UInt64]
237		pub fun getPacksLeftForType(_ type:UInt64) : UInt64
238		pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
239		pub fun borrowAeraPack(id: UInt64): &AeraPack.NFT? 
240		pub fun buyWithSignature(packId: UInt64, signature:String, vault: @FungibleToken.Vault, collectionCapability: Capability<&Collection{NonFungibleToken.Receiver}>) 
241		pub fun claimFreePackWithReservation(packId: UInt64, signature:String, collectionCapability: Capability<&Collection{NonFungibleToken.Receiver}>) 
242	}
243
244	// Collection
245	// A collection of AeraPack NFTs owned by an account
246	//
247	pub resource Collection: NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, CollectionPublic, MetadataViews.ResolverCollection {
248		// dictionary of NFT conforming tokens
249		// NFT is a resource type with an `UInt64` ID field
250		//
251		pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
252
253		pub var nftsPerType: {UInt64:UInt64}
254
255		//This will not work at all on large collecitons
256		// since maps are not ordered in cadence this will pick any random key and that works really well
257		access(self) fun getPackIdForType(_ typeId: UInt64): UInt64? {
258			for key in self.ownedNFTs.keys {
259				if let pack= self.borrowAeraPack(id: key) {
260					if pack.getTypeID() == typeId {
261						return key
262					}
263				}
264			}
265			return nil
266		}
267
268		//this has to be called on the DLQ collection
269		pub fun requeue(packId:UInt64) {
270			let token <- self.withdraw(withdrawID: packId) as! @NFT
271
272			let address=token.resetOpenedBy()
273			let cap=getAccount(address).getCapability<&Collection{NonFungibleToken.Receiver}>(AeraPack.CollectionPublicPath)
274			let receiver = cap.borrow()!
275			receiver.deposit(token: <- token)
276			emit Requeued(packId:packId, address: cap.address)
277		}
278
279		pub fun open(packId: UInt64, receiverCap: Capability<&{NonFungibleToken.Receiver}>) {
280			if !receiverCap.check() {
281				panic("Receiver cap is not valid")
282			}
283			let pack=self.borrowAeraPack(id:packId) ?? panic ("This pack is not in your collection")
284
285			if !pack.getMetadata().canBeOpened() {
286				panic("You cannot open the pack yet")
287			}
288
289			let token <- self.withdraw(withdrawID: packId) as! @AeraPack.NFT
290			token.setOpenedBy(receiverCap)
291
292			// establish the receiver for Redeeming AeraPack
293			let receiver = AeraPack.account.getCapability<&{NonFungibleToken.Receiver}>(AeraPack.OpenedCollectionPublicPath).borrow()!
294
295			let typeId=token.getTypeID()
296			// deposit for consumption
297			receiver.deposit(token: <- token)
298
299			emit Opened(packId:packId, address: self.owner!.address, packTypeId: typeId) 
300		}
301
302
303		pub fun claimFreePackWithReservation(packId: UInt64, signature:String, collectionCapability: Capability<&Collection{NonFungibleToken.Receiver}>) {
304			pre {
305				self.owner!.address == AeraPack.account.address : "You can only buy pack directly from the AeraPack account"
306			}
307
308			let nft <- self.withdraw(withdrawID: packId) as!  @NFT
309			let metadata= nft.getMetadata()
310
311			if !metadata.requiresReservation {
312				panic("This pack type does not require reservation, use the open buy method")
313			}
314
315			if metadata.price != 0.0 {
316				panic("this pack is not free")
317			}
318
319			var time= metadata.buyTime
320			let timestamp=Clock.time()
321			let user=collectionCapability.address
322			var whitelisted= false
323			if let whiteListTime = metadata.whiteListTime {
324
325				//TODO: test
326				if timestamp < whiteListTime {
327					panic("You cannot buy the pack yet")
328				}
329
330				//TODO: test
331				if let float=metadata.floatEventId {
332					whitelisted=AeraPack.hasFloat(floatEventId:float, user:collectionCapability.address)
333				}
334			} else {
335
336				if let float=metadata.floatEventId {
337					//TODO:test
338					if !AeraPack.hasFloat(floatEventId:float, user:collectionCapability.address) {
339						panic("Your user does not have the required float with eventId ".concat(float.toString()))
340					}
341				}
342			}
343
344			if !whitelisted && timestamp < time {
345				panic("You cannot buy the pack yet")
346			}
347
348			let keyList = Crypto.KeyList()
349			let accountKey = self.owner!.keys.get(keyIndex: 0)!.publicKey
350
351			// Adds the public key to the keyList
352			keyList.add(
353				PublicKey(
354					publicKey: accountKey.publicKey,
355					signatureAlgorithm: accountKey.signatureAlgorithm
356				),
357				hashAlgorithm: HashAlgorithm.SHA3_256,
358				weight: 1.0
359			)
360
361			// Creates a Crypto.KeyListSignature from the signature provided in the parameters
362			let signatureSet: [Crypto.KeyListSignature] = []
363			signatureSet.append(
364				Crypto.KeyListSignature(
365					keyIndex: 0,
366					signature: signature.decodeHex()
367				)
368			)
369
370			// Verifies that the signature is valid and that it was generated from the
371			// owner of the collection
372			if(!keyList.verify(signatureSet: signatureSet, signedData: nft.hash.utf8)){
373				panic("Unable to validate the signature for the pack!")
374			}
375
376			let packTypeId=nft.getTypeID()
377
378			collectionCapability.borrow()!.deposit(token: <- nft)
379
380			emit Purchased(packId: packId, address: collectionCapability.address, amount:metadata.price, packTypeId: packTypeId)
381		}
382
383		pub fun buyWithSignature(packId: UInt64, signature:String, vault: @FungibleToken.Vault, collectionCapability: Capability<&Collection{NonFungibleToken.Receiver}>) {
384			pre {
385				self.owner!.address == AeraPack.account.address : "You can only buy pack directly from the AeraPack account"
386			}
387
388			let nft <- self.withdraw(withdrawID: packId) as!  @NFT
389			let metadata= nft.getMetadata()
390
391			if !metadata.requiresReservation {
392				panic("This pack type does not require reservation, use the open buy method")
393			}
394
395			var time= metadata.buyTime
396			let timestamp=Clock.time()
397			let user=collectionCapability.address
398			var whitelisted= false
399			if let whiteListTime = metadata.whiteListTime {
400
401				//TODO: test
402				if timestamp < whiteListTime {
403					panic("You cannot buy the pack yet")
404				}
405
406				//TODO: test
407				if let float=metadata.floatEventId {
408					whitelisted=AeraPack.hasFloat(floatEventId:float, user:collectionCapability.address)
409				}
410			} else {
411
412				if let float=metadata.floatEventId {
413					//TODO:test
414					if !AeraPack.hasFloat(floatEventId:float, user:collectionCapability.address) {
415						panic("Your user does not have the required float with eventId ".concat(float.toString()))
416					}
417				}
418			}
419
420			if !whitelisted && timestamp < time {
421				panic("You cannot buy the pack yet")
422			}
423
424			if vault.getType() != metadata.walletType {
425				panic("The vault sent in is not of the desired type ".concat(metadata.walletType.identifier))
426			}
427
428
429			if metadata.price != vault.balance {
430				panic("Vault does not contain required amount of FT ".concat(metadata.price.toString()))
431			}
432			let keyList = Crypto.KeyList()
433			let accountKey = self.owner!.keys.get(keyIndex: 0)!.publicKey
434
435			// Adds the public key to the keyList
436			keyList.add(
437				PublicKey(
438					publicKey: accountKey.publicKey,
439					signatureAlgorithm: accountKey.signatureAlgorithm
440				),
441				hashAlgorithm: HashAlgorithm.SHA3_256,
442				weight: 1.0
443			)
444
445			// Creates a Crypto.KeyListSignature from the signature provided in the parameters
446			let signatureSet: [Crypto.KeyListSignature] = []
447			signatureSet.append(
448				Crypto.KeyListSignature(
449					keyIndex: 0,
450					signature: signature.decodeHex()
451				)
452			)
453
454			// Verifies that the signature is valid and that it was generated from the
455			// owner of the collection
456			if(!keyList.verify(signatureSet: signatureSet, signedData: nft.hash.utf8)){
457				panic("Unable to validate the signature for the pack!")
458			}
459
460			let packTypeId=nft.getTypeID()
461
462			//if this is a free pack we just destrory the vault
463			if metadata.price==0.0 {
464				destroy <- vault
465			} else {
466				if metadata.royaltyCut != 0.0 && metadata.royaltyCap != nil && metadata.royaltyCap!.check() {
467					metadata.royaltyCap!.borrow()!.deposit(from: <- vault.withdraw(amount: vault.balance * metadata.royaltyCut))
468				} 
469				metadata.wallet.borrow()!.deposit(from: <- vault)
470			}
471			collectionCapability.borrow()!.deposit(token: <- nft)
472
473			emit Purchased(packId: packId, address: collectionCapability.address, amount:metadata.price, packTypeId: packTypeId)
474		}
475
476		// withdraw
477		// Removes an NFT from the collection and moves it to the caller
478		//
479		pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
480			let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("Could not withdraw nft")
481
482			let nft <- token as! @NFT
483
484			let oldNumber= self.nftsPerType[nft.getTypeID()]!
485			self.nftsPerType[nft.getTypeID()]=oldNumber-1
486
487			emit Withdraw(id: nft.id, from: self.owner?.address)
488
489
490			return <-nft
491		}
492
493		// deposit
494		// Takes a NFT and adds it to the collections dictionary
495		// and adds the ID to the id array
496		//
497		pub fun deposit(token: @NonFungibleToken.NFT) {
498			let token <- token as! @AeraPack.NFT
499
500			let id: UInt64 = token.id
501
502			let oldNumber= self.nftsPerType[token.getTypeID()] ?? 0
503			self.nftsPerType[token.getTypeID()]=oldNumber+1
504			// add the new token to the dictionary which removes the old one
505			let oldToken <- self.ownedNFTs[id] <- token
506
507			emit Deposit(id: id, to: self.owner?.address)
508
509			destroy oldToken
510		}
511
512		// getIDs
513		// Returns an array of the IDs that are in the collection
514		//
515		pub fun getIDs(): [UInt64] {
516			return self.ownedNFTs.keys
517		}
518
519		//return the number of packs left of a type
520		pub fun getPacksLeftForType(_ type:UInt64) : UInt64 {
521			return self.nftsPerType[type] ?? 0
522		}
523
524		access(account) fun setPacksLeftForType(_ type:UInt64, amount:UInt64) {
525			self.nftsPerType[type]=amount
526		}
527
528		// borrowNFT
529		// Gets a reference to an NFT in the collection
530		// so that the caller can read its metadata and call its methods
531		//
532		pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
533			return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
534		}
535
536		// borrowAeraPack
537		// Gets a reference to an NFT in the collection as a AeraPack.NFT,
538		// exposing all of its fields.
539		// This is safe as there are no functions that can be called on the AeraPack.
540		//
541		pub fun borrowAeraPack(id: UInt64): &AeraPack.NFT? {
542			if self.ownedNFTs[id] != nil {
543				let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
544				return ref as! &AeraPack.NFT
545			} else {
546				return nil
547			}
548		}
549
550		pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
551			let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
552			let exampleNFT = nft as! &NFT
553			return exampleNFT 
554		}
555
556		// destructor
557		//
558		destroy() {
559			destroy self.ownedNFTs
560		}
561
562		// initializer
563		//
564		init () {
565			self.ownedNFTs <- {}
566			self.nftsPerType= {}
567		}
568	}
569
570	pub fun createEmptyCollection(): @NonFungibleToken.Collection {
571		return <- create Collection()
572	}
573
574	access(account) fun mintNFT(recipient: &{NonFungibleToken.Receiver}, typeId: UInt64, hash: String){
575
576		let nft <- create AeraPack.NFT(typeId: typeId, hash:hash)
577		emit Minted(id: nft.id, typeId:typeId)
578
579		// deposit it in the recipient's account using their reference
580		recipient.deposit(token: <- nft)
581	}
582
583	access(account) fun fulfill(packId: UInt64, rewardIds:[UInt64], salt:String) {
584
585		let openedPacksCollection = AeraPack.account.borrow<&AeraPack.Collection>(from: AeraPack.OpenedCollectionStoragePath)!
586		let pack <- openedPacksCollection.withdraw(withdrawID: packId) as! @AeraPack.NFT
587
588		let receiver= pack.getOpenedBy()
589		if !receiver.check() {
590			emit FulfilledError(packId:packId, address:receiver.address, reason: "The receiver registered in this pack is not valid")
591			self.transferToDLQ(<- pack)
592			return
593		}
594
595		let hash= pack.getHash()
596		let rewards=pack.getMetadata().providerCap
597
598		if !rewards.check() {
599			emit FulfilledError(packId:packId, address:receiver.address, reason: "Cannot borrow provider capability to withdraw nfts")
600			self.transferToDLQ(<- pack)
601			return
602		}
603
604		let receiverAccount=getAccount(receiver.address)
605		let freeStorage=receiverAccount.storageCapacity - receiverAccount.storageUsed
606		Debug.log("Free capacity from account ".concat(freeStorage.toString()))
607
608		if pack.getMetadata().storageRequirement > freeStorage {
609			emit FulfilledError(packId:packId, address:receiver.address, reason: "Not enough flow to hold the content of the pack. Please top up your account")
610			self.transferToDLQ(<- pack)
611			return
612		}
613
614		var string=salt
615		for id in rewardIds {
616			var seperator="-" 
617			if string!=salt {
618				seperator=","
619			}
620			string=string.concat(seperator).concat(id.toString())
621		}
622
623		let digest = HashAlgorithm.SHA3_384.hash(string.utf8)
624		let digestAsString=String.encodeHex(digest)
625		if digestAsString != hash {
626			emit FulfilledError(packId:packId, address:receiver.address, reason: "The content of the pack was not verified with the hash provided at mint")
627			self.transferToDLQ(<- pack)
628			return
629		}
630
631		let target=receiver.borrow()!
632		let source=rewards.borrow()!
633		for reward in rewardIds {
634
635			let viewType= Type<PackRevealData>()
636			let nft=source.borrowViewResolver(id: reward)
637
638			var fields : {String: String}= {}
639			if nft.getViews().contains(viewType) {
640				let view=nft.resolveView(viewType)! as! PackRevealData
641				fields=view.data
642			}
643			let token <- source.withdraw(withdrawID: reward)
644
645			emit PackReveal(
646				packId:packId,
647				address:receiver.address,
648				packTypeId: pack.getTypeID(),
649				rewardId: reward,
650				rewardType: token.getType().identifier,
651				rewardFields: fields,
652				nftPerPack: AeraPackExtraData.getItemsPerPackType(pack.getTypeID())!,
653				packTier: AeraPackExtraData.getTierPerPackType(pack.getTypeID())!
654			)
655			target.deposit(token: <-token)
656		}
657		emit Fulfilled(packId:packId, address:receiver.address)
658
659		destroy pack
660	}
661
662	access(account) fun transferToDLQ(_ pack: @NFT) {
663		let dlq = AeraPack.account.borrow<&AeraPack.Collection>(from: AeraPack.DLQCollectionStoragePath)!
664		dlq.deposit(token: <- pack)
665	}
666
667
668	pub fun getPacksCollection() : &AeraPack.Collection{CollectionPublic} {
669		return AeraPack.account.getCapability<&AeraPack.Collection{AeraPack.CollectionPublic}>(AeraPack.CollectionPublicPath).borrow() ?? panic("Could not borow AeraPack collection")
670	}
671
672	pub fun canBuy(packTypeId:UInt64, user:Address) : Bool {
673
674		let packs=AeraPack.getPacksCollection()
675
676		let packsLeft= packs.getPacksLeftForType(packTypeId)
677		if packsLeft == 0 {
678			return false
679		}
680
681		let packMetadata=AeraPack.getMetadata(typeId: packTypeId)
682
683		if packMetadata==nil {
684			return false
685		}
686		let timestamp=Clock.time() 
687
688		let metadata=packMetadata!
689		var whitelisted= false
690		if let whiteListTime = metadata.whiteListTime {
691			if timestamp < whiteListTime {
692				return false
693			}
694
695			if let float=metadata.floatEventId {
696				whitelisted=AeraPack.hasFloat(floatEventId:float, user:user)
697			}
698		} else {
699			if let float=metadata.floatEventId {
700				if !AeraPack.hasFloat(floatEventId:float, user:user) {
701					return false
702				}
703			}
704		}
705
706		var time= metadata.buyTime
707		if !whitelisted && timestamp < time {
708			return false
709		}
710		return true
711	}
712
713	pub fun hasFloat(floatEventId:UInt64, user:Address) : Bool {
714
715		let float = getAccount(user).getCapability(FLOAT.FLOATCollectionPublicPath).borrow<&FLOAT.Collection{FLOAT.CollectionPublic}>() 
716
717		if float == nil {
718			return false
719		}
720
721		let floatsCollection=float!
722
723		let ids = floatsCollection.getIDs()
724		for id in ids {
725			let nft: &FLOAT.NFT = floatsCollection.borrowFLOAT(id: id)!
726			if nft.eventId==floatEventId {
727				return true
728			}
729		}
730		return false
731	}
732
733	pub fun getOwnerCollection() : Capability<&AeraPack.Collection{MetadataViews.ResolverCollection}> {
734		return AeraPack.account.getCapability<&AeraPack.Collection{MetadataViews.ResolverCollection}>(AeraPack.CollectionPublicPath)
735	}
736
737	// initializer
738	//
739	init() {
740		self.CollectionStoragePath = /storage/AeraPackCollection
741		self.CollectionPublicPath = /public/AeraPackCollection
742
743		self.OpenedCollectionStoragePath = /storage/AeraPackOpenedCollection
744		self.OpenedCollectionPublicPath = /public/AeraPackOpenedCollection
745
746		self.DLQCollectionStoragePath = /storage/AeraPackDLQCollection
747		self.DLQCollectionPublicPath = /public/AeraPackDLQCollection
748
749		self.PackMetadataStoragePath= /storage/AeraPackMetadata
750
751		//this will not be used, we use UUID as id
752		self.totalSupply = 0
753
754		self.packMetadata={}
755
756		// this contract will hold a Collection that AeraPack can be deposited to and Admins can Consume them to transfer nfts to the depositing account
757		let openedCollection <- create Collection()
758		self.account.save(<- openedCollection, to: self.OpenedCollectionStoragePath) 
759		self.account.link<&AeraPack.Collection{NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, AeraPack.CollectionPublic, MetadataViews.ResolverCollection}>(AeraPack.OpenedCollectionPublicPath, target: AeraPack.OpenedCollectionStoragePath)
760
761
762		//a DLQ storage slot so that the opener can put items that cannot be opened/transferred here.
763		let dlqCollection <- create Collection()
764		self.account.save(<- dlqCollection, to: self.DLQCollectionStoragePath) 
765		self.account.link<&AeraPack.Collection{NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, AeraPack.CollectionPublic, MetadataViews.ResolverCollection}>(AeraPack.DLQCollectionPublicPath, target: AeraPack.DLQCollectionStoragePath)
766
767		self.account.save<@NonFungibleToken.Collection>( <- self.createEmptyCollection(), to: self.CollectionStoragePath)
768
769		self.account.link<&AeraPack.Collection{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, AeraPack.CollectionPublic, MetadataViews.ResolverCollection}>(
770			AeraPack.CollectionPublicPath,
771			target: AeraPack.CollectionStoragePath
772		)
773
774		emit ContractInitialized()
775
776	}
777}
778