Smart Contract
MFLPack
A.8ebcbfd516b1da27.MFLPack
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