Smart Contract

MFLPack

A.8ebcbfd516b1da27.MFLPack

Valid From

86,035,932

Deployed

2w ago
Feb 11, 2026, 05:34:26 PM UTC

Dependents

3412 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import ViewResolver from 0x1d7e57aa55817448
3import FungibleToken from 0xf233dcee88fe0abe
4import MetadataViews from 0x1d7e57aa55817448
5import MFLViews from 0x8ebcbfd516b1da27
6import MFLAdmin from 0x8ebcbfd516b1da27
7import MFLPackTemplate from 0x8ebcbfd516b1da27
8
9/**
10  This contract is based on the NonFungibleToken standard on Flow.
11  It allows to mint packs (NFTs), which can then be opened. A pack
12  is always linked to a packTemplate (see MFLPackTemplate contract for more info).
13**/
14
15access(all)
16contract MFLPack: NonFungibleToken {
17
18	// Entitlements
19	access(all)
20	entitlement PackAdminAction
21
22	access(all)
23	entitlement PackAction
24
25	// Events
26	access(all)
27	event ContractInitialized()
28
29    access(all)
30    event Withdraw(id: UInt64, from: Address?)
31
32    access(all)
33    event Deposit(id: UInt64, to: Address?)
34
35	access(all)
36	event Opened(id: UInt64, from: Address?)
37
38	access(all)
39	event Minted(id: UInt64, packTemplateID: UInt64, from: Address?)
40
41	// Named Paths
42	access(all)
43	let CollectionStoragePath: StoragePath
44
45	access(all)
46	let CollectionPublicPath: PublicPath
47
48	access(all)
49	let PackAdminStoragePath: StoragePath
50
51	// Counter for all the Packs ever minted
52	access(all)
53	var totalSupply: UInt64
54
55	access(all)
56	resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver {
57
58		// Unique ID across all packs
59		access(all)
60		let id: UInt64
61
62		// ID used to identify the kind of pack it is
63		access(all)
64		let packTemplateID: UInt64
65
66		init(packTemplateID: UInt64) {
67			MFLPack.totalSupply = MFLPack.totalSupply + 1 as UInt64
68			self.id = MFLPack.totalSupply
69			self.packTemplateID = packTemplateID
70			emit Minted(id: self.id, packTemplateID: packTemplateID, from: self.owner?.address)
71		}
72
73		// Get all supported views for this NFT
74		access(all)
75		view fun getViews(): [Type] {
76			return [
77				Type<MetadataViews.Display>(),
78				Type<MetadataViews.Royalties>(),
79				Type<MetadataViews.NFTCollectionDisplay>(),
80				Type<MetadataViews.NFTCollectionData>(),
81				Type<MetadataViews.ExternalURL>(),
82				Type<MFLViews.PackDataViewV1>()
83			]
84		}
85
86		// Resolve a specific view
87		access(all)
88		fun resolveView(_ view: Type): AnyStruct? {
89			let packTemplateData = MFLPackTemplate.getPackTemplate(id: self.packTemplateID)!
90			switch view {
91				case Type<MetadataViews.Display>():
92					return MetadataViews.Display(
93						name: packTemplateData.name,
94						description: "MFL Pack #".concat(self.id.toString()),
95						thumbnail: MetadataViews.HTTPFile(url: packTemplateData.imageUrl)
96					)
97				case Type<MetadataViews.Royalties>():
98					let royalties: [MetadataViews.Royalty] = []
99					let royaltyReceiverCap = getAccount(MFLAdmin.royaltyAddress()).capabilities.get<&{FungibleToken.Receiver}>(/public/GenericFTReceiver)
100					royalties.append(MetadataViews.Royalty(receiver: royaltyReceiverCap!, cut: 0.05, description: "Creator Royalty"))
101					return MetadataViews.Royalties(royalties)
102				case Type<MetadataViews.NFTCollectionDisplay>():
103                     return MFLPack.resolveContractView(resourceType: Type<@MFLPack.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
104				case Type<MetadataViews.NFTCollectionData>():
105                     return MFLPack.resolveContractView(resourceType: Type<@MFLPack.NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
106				case Type<MetadataViews.ExternalURL>():
107					return MetadataViews.ExternalURL("https://playmfl.com")
108				case Type<MFLViews.PackDataViewV1>():
109					return MFLViews.PackDataViewV1(id: self.id, packTemplate: packTemplateData)
110			}
111			return nil
112		}
113
114		access(all)
115		fun createEmptyCollection(): @{NonFungibleToken.Collection} {
116			return <-MFLPack.createEmptyCollection(nftType: Type<@MFLPack.NFT>())
117		}
118	}
119
120	// Main Collection to manage all the Packs NFT
121	access(all)
122	resource Collection: NonFungibleToken.Collection, ViewResolver.ResolverCollection {
123		// dictionary of NFT conforming tokens
124		// NFT is a resource type with an `UInt64` ID field
125		access(all)
126		var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
127
128		init() {
129			self.ownedNFTs <-{}
130		}
131
132		// withdraw removes an NFT from the collection and moves it to the caller
133		access(NonFungibleToken.Withdraw)
134		fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
135			let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
136
137            emit Withdraw(id: token.id, from: self.owner?.address)
138
139			return <-token
140		}
141
142		access(NonFungibleToken.Withdraw)
143		fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} {
144			// Create a new empty Collection
145			var batchCollection <- create Collection()
146
147			// Iterate through the ids and withdraw them from the Collection
148			for id in ids {
149				batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
150			}
151
152			// Return the withdrawn tokens
153			return <-batchCollection
154		}
155
156		// deposit takes a NFT and adds it to the collections dictionary
157		// and adds the ID to the id array
158		access(all)
159		fun deposit(token: @{NonFungibleToken.NFT}) {
160			let token <- token as! @MFLPack.NFT
161			let id: UInt64 = token.id
162
163			// add the new token to the dictionary which removes the old one
164			let oldToken <- self.ownedNFTs[id] <- token
165
166			emit Deposit(id: id, to: self.owner?.address)
167
168			destroy oldToken
169		}
170
171		// getIDs returns an array of the IDs that are in the collection
172		access(all)
173		view fun getIDs(): [UInt64] {
174			return self.ownedNFTs.keys
175		}
176
177		access(all)
178		view fun getLength(): Int {
179			return self.ownedNFTs.length
180		}
181
182		// Gets a reference to an NFT in the collection so that the caller can read its metadata and call its methods
183		access(all)
184		view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
185			return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
186		}
187
188		access(all)
189		view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
190			if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
191				return nft as &{ViewResolver.Resolver}
192			}
193			return nil
194		}
195
196		access(PackAction)
197		fun openPack(id: UInt64) {
198			let pack <- self.withdraw(withdrawID: id) as! @MFLPack.NFT
199			let packTemplate = MFLPackTemplate.getPackTemplate(id: pack.packTemplateID)!
200
201			// Check if packTemplate is openable or if the owner must wait before opening the pack
202			assert(packTemplate.isOpenable, message: "PackTemplate is not openable")
203
204			// Emit an event which will be processed by the backend to distribute the content of the pack
205			emit Opened(id: pack.id, from: (self.owner!).address)
206			destroy pack
207		}
208
209		access(all)
210		view fun getSupportedNFTTypes(): {Type: Bool} {
211			let supportedTypes: {Type: Bool} = {}
212			supportedTypes[Type<@MFLPack.NFT>()] = true
213			return supportedTypes
214		}
215
216		access(all)
217		view fun isSupportedNFTType(type: Type): Bool {
218			return type == Type<@MFLPack.NFT>()
219		}
220
221		access(all)
222		fun createEmptyCollection(): @{NonFungibleToken.Collection} {
223			return <-MFLPack.createEmptyCollection(nftType: Type<@MFLPack.NFT>())
224		}
225	}
226
227	// Public function that anyone can call to create a new empty collection
228	access(all)
229	fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
230		return <-create Collection()
231	}
232
233	access(all)
234	view fun getContractViews(resourceType: Type?): [Type] {
235		return [
236            Type<MetadataViews.NFTCollectionData>(),
237            Type<MetadataViews.NFTCollectionDisplay>()
238        ]
239	}
240
241	access(all)
242	fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
243        switch viewType {
244            case Type<MetadataViews.NFTCollectionData>():
245                let collectionData = MetadataViews.NFTCollectionData(
246                    storagePath: self.CollectionStoragePath,
247                    publicPath: self.CollectionPublicPath,
248                    publicCollection: Type<&MFLPack.Collection>(),
249                    publicLinkedType: Type<&MFLPack.Collection>(),
250                    createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
251                        return <-MFLPack.createEmptyCollection(nftType: Type<@MFLPack.NFT>())
252                    })
253                )
254                return collectionData
255            case Type<MetadataViews.NFTCollectionDisplay>():
256                return MetadataViews.NFTCollectionDisplay(
257                    name: "MFL Pack Collection",
258                    description: "Build your own football club, make strategic decisions, and live the thrill of real competition. Join a universe where the stakes–and your rivals–are real.",
259                    externalURL: MetadataViews.ExternalURL("https://playmfl.com"),
260                    squareImage: MetadataViews.Media(
261						file: MetadataViews.HTTPFile(url: "https://app.playmfl.com/img/collections/packs/thumbnail.png"),
262						mediaType: "image/png"
263					),
264                    bannerImage: MetadataViews.Media(
265						file: MetadataViews.HTTPFile(url: "https://app.playmfl.com/img/collections/packs/banner.png"),
266						mediaType: "image/png"
267					),
268                    socials: {
269						"twitter": MetadataViews.ExternalURL("https://twitter.com/playMFL"),
270						"discord": MetadataViews.ExternalURL("https://discord.gg/pEDTR4wSPr"),
271						"linkedin": MetadataViews.ExternalURL("https://www.linkedin.com/company/playmfl"),
272						"medium": MetadataViews.ExternalURL("https://medium.com/playmfl")
273					}
274                )
275        }
276        return nil
277    }
278
279	// Deprecated: Only here for backward compatibility.
280	access(all)
281	resource interface PackAdminClaim {}
282
283	access(all)
284	resource PackAdmin: PackAdminClaim {
285		access(all)
286		let name: String
287
288		init() {
289			self.name = "PackAdminClaim"
290		}
291
292		access(PackAdminAction)
293		fun batchMintPack(packTemplateID: UInt64, nbToMint: UInt32): @Collection {
294			MFLPackTemplate.increasePackTemplateCurrentSupply(id: packTemplateID, nbToMint: nbToMint)
295			let newCollection <- create Collection()
296			var i: UInt32 = 0
297			while i < nbToMint {
298				let pack <- create NFT(packTemplateID: packTemplateID)
299				newCollection.deposit(token: <-pack)
300				i = i + 1 as UInt32
301			}
302			return <-newCollection
303		}
304	}
305
306	init() {
307		// Set our named paths
308		self.CollectionStoragePath = /storage/MFLPackCollection
309		self.CollectionPublicPath = /public/MFLPackCollection
310		self.PackAdminStoragePath = /storage/MFLPackAdmin
311
312		// Initialize contract fields
313		self.totalSupply = 0
314
315		// Create a Collection and save it to storage
316		self.account.storage.save<@Collection>(<-create Collection(), to: self.CollectionStoragePath)
317		// Create a public capability for the Collection
318		var capability_1 = self.account.capabilities.storage.issue<&MFLPack.Collection>(MFLPack.CollectionStoragePath)
319		self.account.capabilities.publish(capability_1, at: MFLPack.CollectionPublicPath)
320
321		// Create PackAdmin resource and save it to storage
322		self.account.storage.save(<-create PackAdmin(), to: self.PackAdminStoragePath)
323		emit ContractInitialized()
324	}
325}
326