Smart Contract

AFLPack

A.8f9231920da9af6d.AFLPack

Valid From

88,066,124

Deployed

3d ago
Feb 24, 2026, 11:08:27 PM UTC

Dependents

93 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448 
3import AFLNFT from 0x8f9231920da9af6d 
4// import "FiatToken"   // commented out locally to allow for testing...  uncomment for production
5import USDCFlow from 0xf1ab99c82dee3526
6import StorageHelper from 0x8f9231920da9af6d
7import PackRestrictions from 0x8f9231920da9af6d
8import Burner from 0xf233dcee88fe0abe
9
10access(all) contract AFLPack {
11    // entitlement to restrict
12    access(all) entitlement PackAdmin
13
14    // Events
15    access(all) event PackBought(templateId: UInt64, receiptAddress: Address?)
16    access(all) event PurchaseDetails(buyer: Address, momentsInPack: [{String: UInt64}], pricePaid: UFix64, packID: UInt64, settledOnChain: Bool)
17    access(all) event PackOpened(nftId: UInt64, receiptAddress: Address?)
18    access(all) event HashedPackOpened(nftId: UInt64, packHash: String, packID: UInt64, receiptAddress: Address?)
19    access(all) event HashedPackPurchasedDetails(buyer: Address, packHash: String, pricePaid: UFix64, packID: UInt64, individualPackTemplateId: UInt64, settledOnChain: Bool)
20    access(all) event PackAdminMintedPackContents(moments: [{String: UInt64}], packHash: String, receiptAddress: Address?)
21    
22    // Paths
23    access(all) let PackStoragePath : StoragePath
24    access(all) let PackPublicPath : PublicPath
25
26    // Owner Address
27    access(self) var ownerAddress: Address
28    // removed in the dual testnet version as can't redeploy c1 version with FiatToken
29    // access(contract) let adminRef : Capability<&FiatToken.Vault>
30    
31    access(all) fun buyHashedPack(
32        packHash: String, 
33        receiptAddress: Address, 
34        packTemplateId: UInt64,
35        flowPayment: @{FungibleToken.Vault}) {
36
37        pre {
38            packHash != nil : "pack hash required"
39            receiptAddress != nil : "receipt address must not be null"
40        }
41        // Top up recipients account to ensure they have funds to cover pack storage
42        StorageHelper.topUpAccount(address: receiptAddress)
43
44        // get pack metadata and create individual pack template
45        let packTemplateData: AFLNFT.Template = AFLNFT.getTemplateById(templateId: packTemplateId)
46        let metadata: {String: AnyStruct} = packTemplateData.getImmutableData()
47        metadata["packTemplateId"] = packTemplateId  
48        metadata["packHash"] = packHash
49        
50        // Check funds are correct
51        let priceFromMetadata = metadata["price"]
52        let priceString = priceFromMetadata as! String?
53        let price: UFix64? = UFix64.fromString(priceString!)
54
55        assert(price == flowPayment.balance, message: "Price mismatch")
56
57        // Deposit the payment into the treasury vault
58        let treasuryVault: &USDCFlow.Vault = getAccount(self.ownerAddress)
59                .capabilities.get<&USDCFlow.Vault>(USDCFlow.ReceiverPublicPath)
60                .borrow()
61                ?? panic("Could not get receiver reference to the USDCFlow receiver")
62
63        treasuryVault.deposit(from: <-flowPayment)
64
65        // create a new template and mint the corresponding NFT
66        let individualPackTemplateId: UInt64 = AFLNFT.createTemplate(maxSupply: 1, immutableData: metadata)
67        let receiptAccount: &Account = getAccount(AFLPack.ownerAddress)
68
69        // Mint the Pack NFT
70        AFLNFT.mintNFT(templateInfo: {"id":individualPackTemplateId}, account: receiptAddress)            
71        let newSupply: UInt64 = AFLNFT.allTemplates[packTemplateId]!.incrementIssuedSupply()
72        emit PackBought(templateId: packTemplateId, receiptAddress: receiptAddress)
73        emit HashedPackPurchasedDetails(buyer: receiptAddress, packHash: packHash, pricePaid: price!, packID: packTemplateId, individualPackTemplateId: individualPackTemplateId, settledOnChain: true) 
74    }
75
76    // burns the NFT and emits the event which is picked up by the backend to mint the pack contents
77    access(all) fun openHashedPack(packNFT: @AFLNFT.NFT, receiptAddress: Address) {
78        pre {
79            packNFT != nil : "pack nft must not be null"
80            receiptAddress != nil : "receipt address must not be null"
81        }
82        
83        let packNFTData: AFLNFT.NFTData = AFLNFT.getNFTData(nftId: packNFT.id)
84        let packTemplateData: AFLNFT.Template = AFLNFT.getTemplateById(templateId: packNFTData.templateId)
85        let templateImmutableData: {String: AnyStruct} = packTemplateData.getImmutableData()
86        let packID: UInt64 = packNFTData.templateId
87
88        let packHash: String = templateImmutableData["packHash"]! as! String
89        let packTemplateId: UInt64 = templateImmutableData["packTemplateId"]! as! UInt64
90
91        PackRestrictions.accessCheck(id: packTemplateId)
92
93        emit HashedPackOpened(nftId: packNFT.id, packHash: packHash, packID: packID, receiptAddress: receiptAddress)
94        Burner.burn(<-packNFT)
95    }
96
97    // interface to access the public functions
98    access(all) resource interface PackPublic {
99        // making this function public to call by authorized users
100        access(all) fun openPack(packNFT: @AFLNFT.NFT, receiptAddress: Address)
101    }
102
103    access(all) resource Pack: PackPublic {
104
105        access(PackAdmin) fun updateOwnerAddress(owner:Address){
106            pre{
107                owner != nil: "owner must not be null"
108            }
109            AFLPack.ownerAddress = owner
110        }
111
112        access(PackAdmin) fun buyPackFromAdmin(templateIds: [{String: UInt64}], packTemplateId: UInt64, receiptAddress: Address, price: UFix64) {
113            pre {
114                templateIds.length > 0 : "template id  must not be zero"
115                receiptAddress != nil : "receipt address must not be null"
116            }
117            StorageHelper.topUpAccount(address: receiptAddress)
118
119            var allNftTemplateExists: Bool = true;
120            assert(templateIds.length <= 10, message: "templates limit exceeded")
121            let nftTemplateIds : [{String: UInt64}] = []
122            for tempID in templateIds {
123                let nftTemplateData: AFLNFT.Template = AFLNFT.getTemplateById(templateId: tempID["id"]!)
124                if(nftTemplateData == nil) {
125                    allNftTemplateExists = false
126                    break
127                }
128                nftTemplateIds.append(tempID)
129            }
130
131            let originalPackTemplateData: AFLNFT.Template = AFLNFT.getTemplateById(templateId: packTemplateId)
132            let originalPackTemplateImmutableData: {String: AnyStruct} = originalPackTemplateData.getImmutableData()
133            originalPackTemplateImmutableData["nftTemplates"] = nftTemplateIds
134            originalPackTemplateImmutableData["packTemplateId"] = packTemplateId              
135
136            assert(allNftTemplateExists, message: "Invalid NFTs")
137            let newTemplateId: UInt64 = AFLNFT.createTemplate(maxSupply: 1, immutableData: originalPackTemplateImmutableData)
138
139            let lastIssuedTemplateId: UInt64 = AFLNFT.getLatestTemplateId()
140            AFLNFT.mintNFT(templateInfo: {"id":lastIssuedTemplateId}, account: receiptAddress)
141            let newSupply: UInt64 = AFLNFT.allTemplates[packTemplateId]!.incrementIssuedSupply()
142            emit PackBought(templateId: lastIssuedTemplateId, receiptAddress: receiptAddress)
143            emit PurchaseDetails(buyer: receiptAddress, momentsInPack: templateIds, pricePaid: price, packID: packTemplateId, settledOnChain: false) 
144        }
145
146        // This is to allow the pack to have a custom metadata
147        access(PackAdmin) fun buyPackCustomPackFromAdmin(templateIds: [{String: UInt64}], packTemplateId: UInt64, packMetadata: {String:String}, receiptAddress: Address, price: UFix64) {
148            pre {
149                templateIds.length > 0 : "template id  must not be zero"
150                receiptAddress != nil : "receipt address must not be null"
151            }
152            StorageHelper.topUpAccount(address: receiptAddress)
153
154            var allNftTemplateExists: Bool = true;
155            let nftTemplateIds : [{String: UInt64}] = []
156            for tempID in templateIds {
157                let nftTemplateData: AFLNFT.Template = AFLNFT.getTemplateById(templateId: tempID["id"]!)
158                if(nftTemplateData == nil) {
159                    allNftTemplateExists = false
160                    break
161                }
162                nftTemplateIds.append(tempID)
163            }
164
165            let originalPackTemplateData: AFLNFT.Template = AFLNFT.getTemplateById(templateId: packTemplateId)
166            let originalPackTemplateImmutableData: {String: AnyStruct} = originalPackTemplateData.getImmutableData()
167            originalPackTemplateImmutableData["nftTemplates"] = nftTemplateIds
168            originalPackTemplateImmutableData["packTemplateId"] = packTemplateId
169
170            // add/update custom pack metadata
171            for key in packMetadata.keys {
172                originalPackTemplateImmutableData[key] = packMetadata[key]! 
173            }    
174
175            assert(allNftTemplateExists, message: "Invalid NFTs")
176            let newTemplateId: UInt64 = AFLNFT.createTemplate(maxSupply: 1, immutableData: originalPackTemplateImmutableData)
177
178            let lastIssuedTemplateId: UInt64 = AFLNFT.getLatestTemplateId()
179            AFLNFT.mintNFT(templateInfo: {"id":lastIssuedTemplateId}, account: receiptAddress)
180            let newSupply: UInt64 = AFLNFT.allTemplates[packTemplateId]!.incrementIssuedSupply()
181            emit PackBought(templateId: lastIssuedTemplateId, receiptAddress: receiptAddress)
182            emit PurchaseDetails(buyer: receiptAddress, momentsInPack: templateIds, pricePaid: price, packID: packTemplateId, settledOnChain: false) 
183        }
184
185        access(PackAdmin) fun buyHashedPackFromAdmin(packHash: String, receiptAddress: Address, price: UFix64, packTemplateId: UInt64) {
186            pre {
187                receiptAddress != nil : "receipt address must not be null"
188            }
189            StorageHelper.topUpAccount(address: receiptAddress)
190
191            let originalPackTemplateData: AFLNFT.Template = AFLNFT.getTemplateById(templateId: packTemplateId)
192            let originalPackTemplateImmutableData: {String: AnyStruct} = originalPackTemplateData.getImmutableData()
193            originalPackTemplateImmutableData["packTemplateId"] = packTemplateId              
194
195            originalPackTemplateImmutableData["packHash"] = packHash
196            let individualPackTemplateId: UInt64 = AFLNFT.createTemplate(maxSupply: 1, immutableData: originalPackTemplateImmutableData)
197            AFLNFT.mintNFT(templateInfo: {"id":individualPackTemplateId}, account: receiptAddress)
198
199            let newSupply: UInt64 = AFLNFT.allTemplates[packTemplateId]!.incrementIssuedSupply()
200            emit HashedPackPurchasedDetails(buyer: receiptAddress, packHash: packHash, pricePaid: price, packID: packTemplateId, individualPackTemplateId: individualPackTemplateId, settledOnChain: false) 
201        }
202        
203        access(PackAdmin) fun buyPack(
204            templateIds: [{String: UInt64}], 
205            packTemplateId: UInt64, 
206            receiptAddress: Address, 
207            price: UFix64, 
208            flowPayment: @{FungibleToken.Vault}) {
209
210            pre {
211                templateIds.length > 0 : "template id  must not be zero"
212                flowPayment.balance == price: "Your vault does not have balance to buy NFT"
213                receiptAddress != nil : "receipt address must not be null"
214            }
215            
216            StorageHelper.topUpAccount(address: receiptAddress)
217
218            var allNftTemplateExists: Bool = true;
219            assert(templateIds.length <= 10, message: "templates limit exceeded")
220            let nftTemplateIds : [{String: UInt64}] = []
221            for tempID in templateIds {
222                let nftTemplateData: AFLNFT.Template = AFLNFT.getTemplateById(templateId: tempID["id"]!)
223                if(nftTemplateData == nil) {
224                    allNftTemplateExists = false
225                    break
226                }
227                nftTemplateIds.append(tempID)
228            }
229
230            let originalPackTemplateData: AFLNFT.Template = AFLNFT.getTemplateById(templateId: packTemplateId)
231            let originalPackTemplateImmutableData: {String: AnyStruct} = originalPackTemplateData.getImmutableData()
232            originalPackTemplateImmutableData["nftTemplates"] = nftTemplateIds
233            originalPackTemplateImmutableData["packTemplateId"] = packTemplateId  
234
235            assert(allNftTemplateExists, message: "Invalid NFTs")
236            let newTemplateId: UInt64 = AFLNFT.createTemplate(maxSupply: 1, immutableData: originalPackTemplateImmutableData)
237
238            let lastIssuedTemplateId: UInt64 = AFLNFT.getLatestTemplateId()
239            let receiptAccount: &Account = getAccount(AFLPack.ownerAddress)
240            let recipientCollection = receiptAccount
241                .capabilities.get<&USDCFlow.Vault>(USDCFlow.ReceiverPublicPath)
242                .borrow()
243                ?? panic("Could not get receiver reference to the USDCFlow receiver")
244
245            recipientCollection.deposit(from: <-flowPayment)
246
247            AFLNFT.mintNFT(templateInfo: {"id":lastIssuedTemplateId}, account: receiptAddress)            
248            let newSupply: UInt64 = AFLNFT.allTemplates[packTemplateId]!.incrementIssuedSupply()
249            emit PackBought(templateId: lastIssuedTemplateId, receiptAddress: receiptAddress)
250            emit PurchaseDetails(buyer: receiptAddress, momentsInPack: templateIds, pricePaid: price, packID: packTemplateId, settledOnChain: true) 
251        }
252
253
254         access(all) fun adminMintPackContents(moments: [{String: UInt64}], receiptAddress: Address, packHash: String) {
255            pre {
256                receiptAddress != nil : "receipt address must not be null"
257            }
258            StorageHelper.topUpAccount(address: receiptAddress)
259            
260            // Get moment data for each NFT in the moments array
261            for moment in moments {
262                let nftTemplateData: AFLNFT.Template = AFLNFT.getTemplateById(templateId: moment["id"]!)
263                assert(nftTemplateData != nil, message: "Invalid NFT")
264                AFLNFT.mintNFT(templateInfo: moment,  account: receiptAddress)
265            }
266            emit PackAdminMintedPackContents(moments: moments, packHash: packHash, receiptAddress: receiptAddress)
267        }
268
269        access(all) fun openPack(packNFT: @AFLNFT.NFT, receiptAddress: Address) {
270            pre {
271                packNFT != nil : "pack nft must not be null"
272                receiptAddress != nil : "receipt address must not be null"
273            }
274            StorageHelper.topUpAccount(address: receiptAddress)
275
276            var packNFTData: AFLNFT.NFTData = AFLNFT.getNFTData(nftId: packNFT.id)
277
278            var packTemplateData: AFLNFT.Template = AFLNFT.getTemplateById(templateId: packNFTData.templateId)
279            let templateImmutableData: {String: AnyStruct} = packTemplateData.getImmutableData()
280
281            let allIds: [AnyStruct] = templateImmutableData["nftTemplates"]! as! [AnyStruct]
282            let packSlug: String = templateImmutableData["slug"]! as! String
283
284            let optionalPackTemplateId: AnyStruct? = templateImmutableData["packTemplateId"]
285            if optionalPackTemplateId != nil {
286                let packTemplateId: UInt64 = templateImmutableData["packTemplateId"]! as! UInt64
287                PackRestrictions.accessCheck(id: packTemplateId)
288            }
289
290            assert(allIds.length <= 50, message: "templates limit exceeded")
291            for tempID in allIds {
292                if(packSlug == "ripper-skippers") {
293                    let templateInfo: {String: UInt64} = {"id":tempID as! UInt64}
294                    AFLNFT.mintNFT(templateInfo: templateInfo,  account: receiptAddress)
295                } else {
296                    let templateInfo: {String: UInt64}? = tempID as? {String: UInt64}
297                    AFLNFT.mintNFT(templateInfo: templateInfo!,  account: receiptAddress)
298                }
299            }
300            emit PackOpened(nftId: packNFT.id, receiptAddress: self.owner?.address)
301            Burner.burn(<-packNFT)
302        }
303    }
304
305    init() {
306        self.ownerAddress = self.account.address
307        // self.adminRef = self.account.capabilities.get<&FiatToken.Vault>(/public/VaultReceiverPubPath)
308        self.PackStoragePath = /storage/AFLPack
309        self.PackPublicPath = /public/AFLPack
310        
311        self.account.storage.save(<- create Pack(), to: self.PackStoragePath)
312        
313        let cap: Capability<&{AFLPack.PackPublic}> = 
314            self.account.capabilities.storage.issue<&{PackPublic}>(self.PackStoragePath) 
315        
316        self.account.capabilities.publish(cap, at: self.PackPublicPath)
317    }
318}
319