Smart Contract
Flunks
A.807c3d470888cc48.Flunks
1import FungibleToken from 0xf233dcee88fe0abe
2import ViewResolver from 0x1d7e57aa55817448
3import NonFungibleToken from 0x1d7e57aa55817448
4import MetadataViews from 0x1d7e57aa55817448
5
6
7access(all)
8contract Flunks: NonFungibleToken{
9 access(all)
10 event ContractInitialized()
11
12 access(all)
13 event SetCreated(setID: UInt64)
14
15 access(all)
16 event NFTTemplateCreated(templateID: UInt64, metadata:{ String: String})
17
18 access(all)
19 event Withdraw(id: UInt64, from: Address?)
20
21 access(all)
22 event Deposit(id: UInt64, to: Address?)
23
24 access(all)
25 event Minted(id: UInt64, templateID: UInt64)
26
27 access(all)
28 event TemplateAddedToSet(setID: UInt64, templateID: UInt64)
29
30 access(all)
31 event TemplateLockedFromSet(setID: UInt64, templateID: UInt64)
32
33 access(all)
34 event TemplateUpdated(template: FlunksTemplate)
35
36 access(all)
37 event SetLocked(setID: UInt64)
38
39 access(all)
40 let CollectionStoragePath: StoragePath
41
42 access(all)
43 let CollectionPublicPath: PublicPath
44
45 access(all)
46 let AdminStoragePath: StoragePath
47
48 access(all)
49 var totalSupply: UInt64
50
51 access(all)
52 var nextTemplateID: UInt64
53
54 access(all)
55 var nextSetID: UInt64
56
57 access(self)
58 var FlunksTemplates:{ UInt64: FlunksTemplate}
59
60 access(self)
61 var sets: @{UInt64: Set}
62
63 access(all)
64 resource interface FlunksCollectionPublic{
65 access(all)
66 fun deposit(token: @{NonFungibleToken.NFT}): Void
67
68 access(all)
69 view fun getIDs(): [UInt64]
70
71 access(all)
72 view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
73
74 access(all)
75 fun borrowFlunks(id: UInt64): &Flunks.NFT?{
76 post{
77 result == nil || result?.id == id:
78 "Cannot borrow Flunks reference: The ID of the returned reference is incorrect"
79 }
80 }
81 }
82
83 access(all)
84 struct FlunksTemplate{
85 access(all)
86 let templateID: UInt64
87
88 access(all)
89 var name: String
90
91 access(all)
92 var description: String
93
94 access(all)
95 var locked: Bool
96
97 access(all)
98 var addedToSet: UInt64
99
100 access(self)
101 var metadata:{ String: String}
102
103 access(all)
104 fun getMetadata():{ String: String}{
105 return self.metadata
106 }
107
108 access(all)
109 fun lockTemplate(){
110 self.locked = true
111 }
112
113 access(all)
114 fun updateMetadata(newMetadata:{ String: String}){
115 pre{
116 newMetadata.length != 0:
117 "New Template metadata cannot be empty"
118 }
119 self.metadata = newMetadata
120 }
121
122 access(all)
123 fun markAddedToSet(setID: UInt64){
124 self.addedToSet = setID
125 }
126
127 init(templateID: UInt64, name: String, description: String, metadata:{ String: String}){
128 pre{
129 metadata.length != 0:
130 "New Template metadata cannot be empty"
131 }
132 self.templateID = templateID
133 self.name = name
134 self.description = description
135 self.metadata = metadata
136 self.locked = false
137 self.addedToSet = 0
138 Flunks.nextTemplateID = Flunks.nextTemplateID + 1
139 emit NFTTemplateCreated(templateID: self.templateID, metadata: self.metadata)
140 }
141 }
142
143 access(all)
144 struct PixelUrl{
145 access(all)
146 let url: String
147
148 init(url: String){
149 self.url = url
150 }
151 }
152
153 access(all)
154 resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver{
155 access(all)
156 let id: UInt64
157
158 access(all)
159 let templateID: UInt64
160
161 access(all)
162 let serialNumber: UInt64
163
164 access(all)
165 view fun getViews(): [Type]{
166 return [Type<MetadataViews.Display>(), Type<MetadataViews.NFTCollectionData>(), Type<MetadataViews.NFTCollectionDisplay>(), Type<MetadataViews.ExternalURL>(), Type<MetadataViews.Traits>(), Type<MetadataViews.Edition>(), Type<MetadataViews.Royalties>(), Type<MetadataViews.Serial>()]
167 }
168
169 access(all)
170 fun resolveView(_ view: Type): AnyStruct?{
171 switch view{
172 case Type<Flunks.PixelUrl>():
173 let pixelUri = self.getNFTTemplate().getMetadata()["pixelUri"]
174 if pixelUri == nil{
175 return nil
176 } else{
177 let pixelUrlLen = (pixelUri!).length
178 let encodedPart = (pixelUri!).slice(from: pixelUrlLen - 68, upTo: pixelUrlLen)
179 return "https://storage.googleapis.com/flunks_public/flunks/".concat(encodedPart)
180 }
181 case Type<MetadataViews.Display>():
182 return MetadataViews.Display(name: self.getNFTTemplate().name.concat(" #").concat(self.templateID.toString()), description: self.getNFTTemplate().description, thumbnail: MetadataViews.HTTPFile(url: self.getNFTTemplate().getMetadata()["uri"]!))
183 case Type<MetadataViews.ExternalURL>():
184 return MetadataViews.ExternalURL("https://flunks.net/")
185 case Type<MetadataViews.NFTCollectionData>():
186 return MetadataViews.NFTCollectionData(storagePath: Flunks.CollectionStoragePath, publicPath: Flunks.CollectionPublicPath, publicCollection: Type<&Flunks.Collection>(), publicLinkedType: Type<&Flunks.Collection>(), createEmptyCollectionFunction: fun (): @{NonFungibleToken.Collection}{
187 return <-Flunks.createEmptyCollection(nftType: Type<@Flunks.NFT>())
188 })
189 case Type<MetadataViews.NFTCollectionDisplay>():
190 let bannerMedia = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://storage.googleapis.com/flunks_public/website-assets/banner_2023.png"), mediaType: "image/png")
191 let logoFull = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://storage.googleapis.com/flunks_public/images/F%20for%20flowty.png"), mediaType: "image/png")
192 return MetadataViews.NFTCollectionDisplay(name: "Flunks", description: "Flunks are cute but mischievous high-schoolers wreaking havoc #onFlow", externalURL: MetadataViews.ExternalURL("https://flunks.net/"), squareImage: logoFull, bannerImage: bannerMedia, socials:{ "twitter": MetadataViews.ExternalURL("https://twitter.com/flunks_nft")})
193 case Type<MetadataViews.Traits>():
194 let excludedTraits = ["mimetype", "uri", "pixelUri", "path", "cid"]
195 let traitsView = MetadataViews.dictToTraits(dict: self.getNFTTemplate().getMetadata(), excludedNames: excludedTraits)
196 return traitsView
197 case Type<MetadataViews.Edition>():
198 return MetadataViews.Edition(name: "Flunks", number: self.templateID, max: 9999)
199 case Type<MetadataViews.Serial>():
200 return MetadataViews.Serial(self.templateID)
201 case Type<MetadataViews.Royalties>():
202 // Note: replace the address for different merchant accounts across various networks
203 let merchant = getAccount(0x0cce91b08cb58286)
204 return MetadataViews.Royalties([MetadataViews.Royalty(receiver: merchant.capabilities.get<&{FungibleToken.Receiver}>(/public/dapperUtilityCoinReceiver)!, cut: 0.05, description: "Flunks creator royalty in DUC")])
205 }
206 return nil
207 }
208
209 access(all)
210 fun getNFTTemplate(): FlunksTemplate{
211 return Flunks.FlunksTemplates[self.templateID]!
212 }
213
214 access(all)
215 fun getNFTMetadata():{ String: String}{
216 return (Flunks.FlunksTemplates[self.templateID]!).getMetadata()
217 }
218
219 access(all)
220 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
221 return <-create Collection()
222 }
223
224 init(initID: UInt64, initTemplateID: UInt64, serialNumber: UInt64){
225 self.id = initID
226 self.templateID = initTemplateID
227 self.serialNumber = serialNumber
228 }
229 }
230
231 access(all)
232 resource Collection: FlunksCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.Collection, NonFungibleToken.CollectionPublic, ViewResolver.ResolverCollection{
233 access(all)
234 var ownedNFTs: @{UInt64:{ NonFungibleToken.NFT}}
235
236 access(NonFungibleToken.Withdraw)
237 fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT}{
238 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
239 emit Withdraw(id: token.id, from: self.owner?.address)
240 return <-token
241 }
242
243 access(all)
244 fun deposit(token: @{NonFungibleToken.NFT}): Void{
245 let token <- token as! @Flunks.NFT
246 let id: UInt64 = token.id
247 let oldToken <- self.ownedNFTs[id] <- token
248 emit Deposit(id: id, to: self.owner?.address)
249 destroy oldToken
250 }
251
252 access(all)
253 fun batchDeposit(collection: @Collection){
254 let keys = collection.getIDs()
255 for key in keys{
256 self.deposit(token: <-collection.withdraw(withdrawID: key))
257 }
258 destroy collection
259 }
260
261 access(all)
262 view fun getIDs(): [UInt64]{
263 return self.ownedNFTs.keys
264 }
265
266 access(all)
267 view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?{
268 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
269 }
270
271 access(all)
272 fun borrowFlunks(id: UInt64): &Flunks.NFT?{
273 if self.ownedNFTs[id] != nil{
274 let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
275 return ref as! &Flunks.NFT
276 } else{
277 return nil
278 }
279 }
280
281 access(all)
282 view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?{
283 let nft = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
284 let exampleNFT = nft as! &Flunks.NFT
285 return exampleNFT as &{ViewResolver.Resolver}
286 }
287
288 access(all)
289 view fun getSupportedNFTTypes(): {Type: Bool} {
290 return {Type<@Flunks.NFT>(): true}
291 }
292
293 access(all)
294 view fun isSupportedNFTType(type: Type): Bool {
295 return type == Type<@Flunks.NFT>()
296 }
297
298 access(all)
299 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
300 return <-create Collection()
301 }
302
303 init(){
304 self.ownedNFTs <-{}
305 }
306 }
307
308 access(all)
309 fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection}{
310 return <-create Collection()
311 }
312
313 access(all)
314 resource Set{
315 access(all)
316 let setID: UInt64
317
318 access(all)
319 let name: String
320
321 access(self)
322 var templateIDs: [UInt64]
323
324 access(self)
325 var availableTemplateIDs: [UInt64]
326
327 access(self)
328 var lockedTemplates:{ UInt64: Bool}
329
330 access(all)
331 var locked: Bool
332
333 access(all)
334 var nextSetSerialNumber: UInt64
335
336 access(all)
337 var isPublic: Bool
338
339 init(name: String){
340 self.name = name
341 self.setID = Flunks.nextSetID
342 self.templateIDs = []
343 self.lockedTemplates ={}
344 self.locked = false
345 self.availableTemplateIDs = []
346 self.nextSetSerialNumber = 1
347 self.isPublic = false
348 Flunks.nextSetID = Flunks.nextSetID + 1
349 emit SetCreated(setID: self.setID)
350 }
351
352 access(all)
353 fun getAvailableTemplateIDs(): [UInt64]{
354 return self.availableTemplateIDs
355 }
356
357 access(all)
358 fun makeSetPublic(){
359 self.isPublic = true
360 }
361
362 access(all)
363 fun makeSetPrivate(){
364 self.isPublic = false
365 }
366
367 access(all)
368 fun addTemplate(templateID: UInt64){
369 pre{
370 Flunks.FlunksTemplates[templateID] != nil:
371 "Template doesn't exist"
372 !self.locked:
373 "Cannot add template - set is locked"
374 !self.templateIDs.contains(templateID):
375 "Cannot add template - template is already added to the set"
376 !((Flunks.FlunksTemplates[templateID]!).addedToSet != 0):
377 "Cannot add template - template is already added to another set"
378 }
379 self.templateIDs.append(templateID)
380 self.availableTemplateIDs.append(templateID)
381 self.lockedTemplates[templateID] = false
382 (Flunks.FlunksTemplates[templateID]!).markAddedToSet(setID: self.setID)
383 emit TemplateAddedToSet(setID: self.setID, templateID: templateID)
384 }
385
386 access(all)
387 fun addTemplates(templateIDs: [UInt64]){
388 for template in templateIDs{
389 self.addTemplate(templateID: template)
390 }
391 }
392
393 access(all)
394 fun lockTemplate(templateID: UInt64){
395 pre{
396 self.lockedTemplates[templateID] != nil:
397 "Cannot lock the template: Template is locked already!"
398 !self.availableTemplateIDs.contains(templateID):
399 "Cannot lock a not yet minted template!"
400 }
401 if !self.lockedTemplates[templateID]!{
402 self.lockedTemplates[templateID] = true
403 emit TemplateLockedFromSet(setID: self.setID, templateID: templateID)
404 }
405 }
406
407 access(all)
408 fun lockAllTemplates(){
409 for template in self.templateIDs{
410 self.lockTemplate(templateID: template)
411 }
412 }
413
414 access(all)
415 fun lock(){
416 if !self.locked{
417 self.locked = true
418 emit SetLocked(setID: self.setID)
419 }
420 }
421
422 access(all)
423 fun updateTemplateMetadata(templateID: UInt64, newMetadata:{ String: String}): FlunksTemplate{
424 pre{
425 Flunks.FlunksTemplates[templateID] != nil:
426 "Template doesn't exist"
427 !self.locked:
428 "Cannot edit template - set is locked"
429 }
430 (Flunks.FlunksTemplates[templateID]!).updateMetadata(newMetadata: newMetadata)
431 emit TemplateUpdated(template: Flunks.FlunksTemplates[templateID]!)
432 return Flunks.FlunksTemplates[templateID]!
433 }
434 }
435
436 access(all)
437 resource Admin{
438 access(all)
439 fun borrowSet(setID: UInt64): &Set{
440 pre{
441 Flunks.sets[setID] != nil:
442 "Cannot borrow Set: The Set doesn't exist"
443 }
444 return (&Flunks.sets[setID] as &Set?)!
445 }
446
447 access(all)
448 fun updateFlunksTemplate(templateID: UInt64, newMetadata:{ String: String}){
449 pre{
450 Flunks.FlunksTemplates.containsKey(templateID):
451 "Template does not exit."
452 }
453 (Flunks.FlunksTemplates[templateID]!).updateMetadata(newMetadata: newMetadata)
454 }
455 }
456
457 access(all)
458 fun getFlunksTemplateByID(templateID: UInt64): Flunks.FlunksTemplate{
459 return Flunks.FlunksTemplates[templateID]!
460 }
461
462 access(all)
463 fun getFlunksTemplates():{ UInt64: Flunks.FlunksTemplate}{
464 return Flunks.FlunksTemplates
465 }
466
467 access(all)
468 fun getAvailableTemplateIDsInSet(setID: UInt64): [UInt64]{
469 pre{
470 Flunks.sets[setID] != nil:
471 "Cannot borrow Set: The Set doesn't exist"
472 }
473 let set = (&Flunks.sets[setID] as &Set?)!
474 return set.getAvailableTemplateIDs()
475 }
476
477 access(all) view fun getContractViews(resourceType: Type?): [Type] {
478 return [
479 Type<MetadataViews.NFTCollectionData>(),
480 Type<MetadataViews.NFTCollectionDisplay>(),
481 Type<MetadataViews.EVMBridgedMetadata>(),
482 Type<MetadataViews.Royalties>()
483 ]
484 }
485
486 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
487 switch viewType {
488 case Type<MetadataViews.NFTCollectionData>():
489 return MetadataViews.NFTCollectionData(
490 storagePath: Flunks.CollectionStoragePath,
491 publicPath: Flunks.CollectionPublicPath,
492 publicCollection: Type<&Flunks.Collection>(),
493 publicLinkedType: Type<&Flunks.Collection>(),
494 createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
495 return <-Flunks.createEmptyCollection(nftType: Type<@Flunks.NFT>())
496 })
497 )
498 case Type<MetadataViews.NFTCollectionDisplay>():
499 return MetadataViews.NFTCollectionDisplay(
500 name: "Flunks",
501 description: "Flunks are cute but mischievous high-schoolers wreaking havoc #onFlow",
502 externalURL: MetadataViews.ExternalURL("https://flunks.net/"),
503 squareImage: MetadataViews.Media(
504 file: MetadataViews.HTTPFile(url: "https://storage.googleapis.com/flunks_public/images/F%20for%20flowty.png"),
505 mediaType: "image/png"
506 ),
507 bannerImage: MetadataViews.Media(
508 file: MetadataViews.HTTPFile(url: "https://storage.googleapis.com/flunks_public/website-assets/banner_2023.png"),
509 mediaType: "image/png"
510 ),
511 socials: {
512 "twitter": MetadataViews.ExternalURL("https://twitter.com/flunks_nft")
513 }
514 )
515 case Type<MetadataViews.EVMBridgedMetadata>():
516 return MetadataViews.EVMBridgedMetadata(
517 name: "Flunks",
518 symbol: "FLNK",
519 uri: MetadataViews.URI(
520 baseURI: "https://flunks.net/nft/",
521 value: ""
522 )
523 )
524 case Type<MetadataViews.Royalties>():
525 return MetadataViews.Royalties([
526 MetadataViews.Royalty(
527 receiver: getAccount(0xbfffec679fff3a94)
528 .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver),
529 cut: 0.10,
530 description: "Flunks creator royalty"
531 )
532 ])
533 }
534 return nil
535 }
536
537 init(){
538 self.CollectionStoragePath = /storage/FlunksCollection
539 self.CollectionPublicPath = /public/FlunksCollection
540 self.AdminStoragePath = /storage/FlunksAdmin
541 self.totalSupply = 0
542 self.nextTemplateID = 1
543 self.nextSetID = 1
544 self.sets <-{}
545 self.FlunksTemplates ={}
546 let admin <- create Admin()
547 self.account.storage.save(<-admin, to: self.AdminStoragePath)
548 emit ContractInitialized()
549 }
550}
551