Smart Contract
AFLNFT
A.8f9231920da9af6d.AFLNFT
1import NonFungibleToken from 0x1d7e57aa55817448
2import FungibleToken from 0xf233dcee88fe0abe
3import MetadataViews from 0x1d7e57aa55817448
4import ViewResolver from 0x1d7e57aa55817448
5import Burner from 0xf233dcee88fe0abe
6import AFLBadges from 0x8f9231920da9af6d
7import AFLMetadataHelper from 0x8f9231920da9af6d
8import AFLBurnRegistry from 0x8f9231920da9af6d
9
10access(all) contract AFLNFT: NonFungibleToken {
11 // Events
12 access(all) event ContractInitialized()
13 access(all) event Withdraw(id: UInt64, from: Address?)
14 access(all) event Deposit(id: UInt64, to: Address?)
15 access(all) event NFTDestroyed(id: UInt64)
16 access(all) event NFTMinted(nftId: UInt64, templateId: UInt64, mintNumber: UInt64)
17 access(all) event TemplateCreated(templateId: UInt64, maxSupply: UInt64)
18 access(all) event NFTBurnt(nftId: UInt64, templateId: UInt64, mintNumber: UInt64)
19
20 // Paths
21 access(all) let CollectionStoragePath: StoragePath
22 access(all) let CollectionPublicPath: PublicPath
23
24 // Latest template-id
25 access(all) var lastIssuedTemplateId: UInt64
26
27 // Total supply of all NFTs that are minted using this contract
28 access(all) var totalSupply: UInt64
29
30 // A dictionary that stores all Templates against it's template-id.
31 access(account) var allTemplates: {UInt64: Template}
32
33 // A dictionary that stores all NFTs against it's nft-id.
34 access(self) var allNFTs: {UInt64: NFTData}
35
36 // A structure that contain all the data and methods related to Template
37 access(all) struct Template {
38 access(all) let templateId: UInt64
39 access(all) var maxSupply: UInt64
40 access(all) var issuedSupply: UInt64
41 access(contract) var immutableData: {String: AnyStruct}
42
43 init(maxSupply: UInt64, immutableData: {String: AnyStruct}) {
44 pre {
45 maxSupply > 0 : "MaxSupply must be greater than zero"
46 immutableData != nil: "ImmutableData must not be nil"
47 immutableData.length != 0: "New template data cannot be empty"
48 }
49
50 self.templateId = AFLNFT.lastIssuedTemplateId
51 self.maxSupply = maxSupply
52 self.immutableData = immutableData
53 self.issuedSupply = 0
54 }
55 access(all) view fun getImmutableData(): {String:AnyStruct} {
56 return self.immutableData
57 }
58
59 access(account) fun updateImmutableData(_ data: {String: AnyStruct}) {
60 for key in data.keys {
61 self.immutableData[key] = data[key]!
62 }
63 }
64
65 access(account) fun updateMaxSupply(_ maxSupply: UInt64) {
66 self.maxSupply = maxSupply
67 }
68
69 // a method to increment issued supply for template
70 access(account) fun incrementIssuedSupply(): UInt64 {
71 pre {
72 self.issuedSupply < self.maxSupply: "Template reached max supply"
73 }
74 self.issuedSupply = self.issuedSupply + 1
75 return self.issuedSupply
76 }
77
78 access(account) fun decrementIssuedSupply(): UInt64 {
79 self.issuedSupply = self.issuedSupply - 1
80 return self.issuedSupply
81 }
82
83 access(account) fun addBadges() {
84 self.immutableData["badges"] = AFLBadges.getBadgesForTemplate(id: self.templateId)
85 }
86
87 access(account) fun addMetadata() {
88 for key in AFLMetadataHelper.getMetadataForTemplate(id: self.templateId).keys {
89 self.immutableData[key] = AFLMetadataHelper.getMetadataForTemplate(id: self.templateId)[key]!
90 }
91 }
92 access(account) fun updateSupplyInformation() {
93 self.immutableData["issuedSupply"] = self.issuedSupply
94 self.immutableData["circulatingSupply"] = self.issuedSupply - AFLBurnRegistry.getBurnDetails(templateId: self.templateId)
95 self.immutableData["burntSupply"] = AFLBurnRegistry.getBurnDetails(templateId: self.templateId)
96 }
97 }
98 // A structure that link template and mint-no of NFT
99 access(all) struct NFTData {
100 access(all) let templateId: UInt64
101 access(all) let mintNumber: UInt64
102
103 init(templateId: UInt64, mintNumber: UInt64) {
104 self.templateId = templateId
105 self.mintNumber = mintNumber
106 }
107 }
108 // The resource that represents the AFLNFT NFTs
109 //
110 access(all) resource NFT: NonFungibleToken.NFT, Burner.Burnable {
111 access(all) let id: UInt64
112
113 init(templateId: UInt64, mintNumber: UInt64) {
114 AFLNFT.totalSupply = AFLNFT.totalSupply + 1
115
116 self.id = AFLNFT.totalSupply
117 AFLNFT.allNFTs[self.id] = NFTData(templateId: templateId, mintNumber: mintNumber)
118
119 emit NFTMinted(nftId: self.id, templateId: templateId, mintNumber: mintNumber)
120 }
121
122
123 access (contract) fun burnCallback() {
124 let nftData: &AFLNFT.NFTData = &AFLNFT.allNFTs[self.id]!
125 let templateId: UInt64 = nftData.templateId
126 let mintNumber: UInt64 = nftData.mintNumber
127 let templateRef: &AFLNFT.Template = &AFLNFT.allTemplates[templateId]!
128 // templateRef.decrementIssuedSupply()
129 // AFLNFT.totalSupply = AFLNFT.totalSupply - 1
130 // AFLNFT.allNFTs[self.id] = nil
131 emit NFTDestroyed(id: self.id)
132 emit NFTBurnt(nftId: self.id, templateId: templateId, mintNumber: mintNumber)
133 AFLBurnRegistry.burn(templateId: templateId)
134 }
135
136 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
137 return <- AFLNFT.createEmptyCollection(nftType: Type<@AFLNFT.NFT>())
138 }
139
140 access(all) view fun getViews(): [Type] {
141 return [
142 Type<MetadataViews.Display>(),
143 Type<MetadataViews.Royalties>(),
144 Type<MetadataViews.Editions>(),
145 Type<MetadataViews.ExternalURL>(),
146 Type<MetadataViews.NFTCollectionData>(),
147 Type<MetadataViews.NFTCollectionDisplay>(),
148 Type<MetadataViews.Serial>(),
149 Type<MetadataViews.Traits>()
150 // Type<MetadataViews.EVMBridgedMetadata>()
151 ]
152 }
153
154 access(all) fun resolveView(_ view: Type): AnyStruct? {
155 return nil
156 }
157 }
158
159 access(all)resource interface AFLNFTCollectionPublic {
160 access(all) fun deposit(token: @{NonFungibleToken.NFT})
161 access(all) view fun getIDs(): [UInt64]
162 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
163 access(all) view fun borrowAFLNFT(id: UInt64): &AFLNFT.NFT? {
164 // If the result isn't nil, the id of the returned reference
165 // should be the same as the argument to the function
166 post {
167 (result == nil) || (result?.id == id):
168 "Cannot borrow AFLNFT reference: The ID of the returned reference is incorrect"
169 }
170 }
171 }
172
173 // Collection is a resource that every user who owns NFTs
174 // will store in their account to manage their NFTS
175 //
176 access(all) resource Collection: AFLNFTCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.Collection, NonFungibleToken.CollectionPublic, ViewResolver.ResolverCollection {
177 // AFLNFTCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic {
178 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
179
180 init() {
181 self.ownedNFTs <- {}
182 }
183
184 /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
185 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
186 let supportedTypes: {Type: Bool} = {}
187 supportedTypes[Type<@AFLNFT.NFT>()] = true
188 return supportedTypes
189 }
190
191 /// Returns whether or not the given type is accepted by the collection
192 /// A collection that can accept any type should just return true by default
193 access(all) view fun isSupportedNFTType(type: Type): Bool {
194 return type == Type<@AFLNFT.NFT>()
195 }
196
197 access(all) fun forEachID(_ f: fun (UInt64): Bool): Void {}
198
199 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
200 assert(withdrawID != 87707, message: "Transfer of this NFT is not allowed!") // 87707 is the id of 2021 AFL Genesis Ball
201 let data = AFLNFT.getNFTData(nftId: withdrawID)
202 // assert(data.templateId != 0, message: "Transfer of this NFT is not allowed!") // yet to implement template transfer guard
203 let token: @{NonFungibleToken.NFT} <- self.ownedNFTs.remove(key: withdrawID)
204 ?? panic("Cannot withdraw: moment does not exist in the collection")
205
206 emit Withdraw(id: token.id, from: self.owner?.address)
207 return <-token
208 }
209
210 access(all) view fun getIDs(): [UInt64] {
211 return self.ownedNFTs.keys
212 }
213
214 access(all) view fun getLength(): Int {
215 return self.ownedNFTs.length
216 }
217
218 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
219 let token: @AFLNFT.NFT <- token as! @AFLNFT.NFT
220 let id: UInt64 = token.id
221 let oldToken: @{NonFungibleToken.NFT}? <- self.ownedNFTs[id] <- token
222 if self.owner?.address != nil {
223 emit Deposit(id: id, to: self.owner?.address)
224 }
225 Burner.burn(<-oldToken)
226 }
227
228 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
229 return (&self.ownedNFTs[id])
230 }
231
232 access(all) view fun borrowAFLNFT(id: UInt64): &AFLNFT.NFT? {
233 if self.ownedNFTs[id] != nil {
234 let ref: &{NonFungibleToken.NFT} = (&self.ownedNFTs[id])!
235 return ref as! &AFLNFT.NFT?
236 }
237 else {
238 return nil
239 }
240 }
241
242 /// createEmptyCollection creates an empty Collection of the same type
243 /// and returns it to the caller
244 /// @return A an empty collection of the same type
245 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
246 return <- AFLNFT.createEmptyCollection(nftType: Type<@AFLNFT.NFT>())
247 }
248
249 }
250
251 // account accessible methods called through AFLAdmin proxy contract/resource
252
253 access(account) fun createTemplate(maxSupply: UInt64, immutableData: {String: AnyStruct}): UInt64 {
254 let newTemplate: AFLNFT.Template = Template(maxSupply: maxSupply, immutableData: immutableData)
255 AFLNFT.allTemplates[AFLNFT.lastIssuedTemplateId] = newTemplate
256 emit TemplateCreated(templateId: AFLNFT.lastIssuedTemplateId, maxSupply: maxSupply)
257 AFLNFT.lastIssuedTemplateId = AFLNFT.lastIssuedTemplateId + 1
258 return AFLNFT.lastIssuedTemplateId - 1
259 }
260
261 access(account) fun mintNFT(templateInfo: {String: UInt64}, account: Address) {
262 pre {
263 account != nil: "invalid receipt Address"
264 AFLNFT.allTemplates[templateInfo["id"]!] != nil: "Template Id must be valid"
265 }
266 let receiptAccount: &Account = getAccount(account)
267
268
269 let recipientCollection: &{AFLNFT.AFLNFTCollectionPublic} = receiptAccount
270 .capabilities.get<&{AFLNFT.AFLNFTCollectionPublic}>(AFLNFT.CollectionPublicPath)
271 .borrow()
272 ?? panic("Could not get receiver reference to the NFT Collection")
273
274
275 let mintNumberFromSupply = AFLNFT.allTemplates[templateInfo["id"]!]!.incrementIssuedSupply()
276 let mintNumber = templateInfo["serial"] ?? mintNumberFromSupply
277 var newNFT: @NFT <- create NFT(templateId: templateInfo["id"]!, mintNumber: mintNumber)
278 recipientCollection.deposit(token: <-newNFT)
279 }
280
281 access(account) fun mintAndReturnNFT(templateInfo: {String: UInt64}): @{NonFungibleToken.NFT} {
282 pre {
283 AFLNFT.allTemplates[templateInfo["id"]!] != nil: "Template Id must be valid"
284 }
285 let mintNumberFromSupply: UInt64 = AFLNFT.allTemplates[templateInfo["id"]!]!.incrementIssuedSupply()
286 let mintNumber: UInt64 = templateInfo["serial"] ?? mintNumberFromSupply
287 var newNFT: @NFT <- create NFT(templateId: templateInfo["id"]!, mintNumber: mintNumber)
288 return <-newNFT
289 }
290
291 // public functions
292
293 // method to create empty Collection
294 /// @{NonFungibleToken.Collection}
295 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
296 return <- create Collection()
297 }
298
299 access(all) view fun getContractViews(resourceType: Type?): [Type] {
300 return [
301 Type<MetadataViews.NFTCollectionData>()
302 ]
303 }
304
305 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
306 switch viewType {
307
308 case Type<MetadataViews.NFTCollectionData>():
309 let collectionData: MetadataViews.NFTCollectionData = MetadataViews.NFTCollectionData(
310 storagePath: self.CollectionStoragePath,
311 publicPath: self.CollectionPublicPath,
312 publicCollection: Type<&AFLNFT.Collection>(),
313 publicLinkedType: Type<&AFLNFT.Collection>(),
314 createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
315 return <-AFLNFT.createEmptyCollection(nftType: Type<@AFLNFT.NFT>())
316 })
317 )
318 return collectionData
319
320 case Type<MetadataViews.NFTCollectionDisplay>():
321 let media: MetadataViews.Media = MetadataViews.Media(
322 file: MetadataViews.HTTPFile(
323 url: "https://www.aflmint.com.au/assets/logo/Mint-logo.png"
324 ),
325 mediaType: "image/png"
326 )
327 return MetadataViews.NFTCollectionDisplay(
328 name: "The AFL Mint Collection",
329 description: "The official AFL NFT collection on Flow",
330 externalURL: MetadataViews.ExternalURL("https://www.aflmint.com.au/"),
331 squareImage: media,
332 bannerImage: media,
333 socials: {
334 "twitter": MetadataViews.ExternalURL("https://twitter.com/AFLMint"),
335 "discord": MetadataViews.ExternalURL("https://discord.com/invite/aflmintofficial"),
336 "instagram": MetadataViews.ExternalURL("https://www.instagram.com/aflmint/"),
337 "youtube": MetadataViews.ExternalURL("https://www.youtube.com/channel/UCT1E0Rmm6_DY99dRTFI1hyQ"),
338 "facebook": MetadataViews.ExternalURL("https://facebook.com/aflmint")
339 }
340 )
341 }
342 return nil
343 }
344
345
346 // access(all) fun
347
348 //method to get all templates
349 access(all) fun getAllTemplates(): {UInt64: Template} {
350 return AFLNFT.allTemplates
351 }
352
353 access(all) fun borrowTemplate(id: UInt64): &Template {
354 return &AFLNFT.allTemplates[id]!
355 }
356
357 //method to get the latest template id
358 access(all) fun getLatestTemplateId() : UInt64 {
359 return AFLNFT.lastIssuedTemplateId - 1
360 }
361
362 //method to get template by id
363 access(all) fun getTemplateById(templateId: UInt64): Template {
364 pre {
365 AFLNFT.allTemplates[templateId] != nil: "Template id does not exist"
366 }
367 let template = AFLNFT.allTemplates[templateId]!
368 template.addBadges()
369 template.addMetadata()
370 template.updateSupplyInformation()
371 return template
372 }
373
374 //method to get nft-data by id
375 access(all) fun getNFTData(nftId: UInt64): NFTData {
376 pre {
377 AFLNFT.allNFTs[nftId] != nil:"nft id does not exist"
378 }
379 return AFLNFT.allNFTs[nftId]!
380 }
381
382 init() {
383 self.lastIssuedTemplateId = 1
384 self.totalSupply = 0
385 self.allTemplates = {}
386 self.allNFTs = {}
387 self.CollectionStoragePath = /storage/AFLNFTCollection
388 self.CollectionPublicPath = /public/AFLNFTCollection
389 }
390}
391