Smart Contract
AFLPack
A.8f9231920da9af6d.AFLPack
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