Smart Contract
AeraPack
A.30cf5dcf6ea8d379.AeraPack
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