@flowtyio/flow-contracts 0.0.17 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1161 @@
1
+ // MADE BY: Emerald City, Jacob Tucker
2
+
3
+ // This contract is for FLOAT, a proof of participation platform
4
+ // on Flow. It is similar to POAP, but a lot, lot cooler. ;)
5
+
6
+ // The main idea is that FLOATs are simply NFTs. They are minted from
7
+ // a FLOATEvent, which is basically an event that a host starts. For example,
8
+ // if I have a Twitter space and want to create an event for it, I can create
9
+ // a new FLOATEvent in my FLOATEvents collection and mint FLOATs to people
10
+ // from this Twitter space event representing that they were there.
11
+
12
+ // The complicated part is the FLOATVerifiers contract. That contract
13
+ // defines a list of "verifiers" that can be tagged onto a FLOATEvent to make
14
+ // the claiming more secure. For example, a host can decide to put a time
15
+ // constraint on when users can claim a FLOAT. They would do that by passing in
16
+ // a Timelock struct (defined in FLOATVerifiers.cdc) with a time period for which
17
+ // users can claim.
18
+
19
+ // For a whole list of verifiers, see FLOATVerifiers.cdc
20
+
21
+ import "NonFungibleToken"
22
+ import "MetadataViews"
23
+ import "FungibleToken"
24
+ import "FlowToken"
25
+ import "FindViews"
26
+ import "ViewResolver"
27
+
28
+ pub contract FLOAT: NonFungibleToken, ViewResolver {
29
+
30
+ /***********************************************/
31
+ /******************** PATHS ********************/
32
+ /***********************************************/
33
+
34
+ pub let FLOATCollectionStoragePath: StoragePath
35
+ pub let FLOATCollectionPublicPath: PublicPath
36
+ pub let FLOATEventsStoragePath: StoragePath
37
+ pub let FLOATEventsPublicPath: PublicPath
38
+ pub let FLOATEventsPrivatePath: PrivatePath
39
+
40
+ /************************************************/
41
+ /******************** EVENTS ********************/
42
+ /************************************************/
43
+
44
+ pub event ContractInitialized()
45
+ pub event FLOATMinted(id: UInt64, eventHost: Address, eventId: UInt64, eventImage: String, recipient: Address, serial: UInt64)
46
+ pub event FLOATClaimed(id: UInt64, eventHost: Address, eventId: UInt64, eventImage: String, eventName: String, recipient: Address, serial: UInt64)
47
+ pub event FLOATDestroyed(id: UInt64, eventHost: Address, eventId: UInt64, eventImage: String, serial: UInt64)
48
+ pub event FLOATTransferred(id: UInt64, eventHost: Address, eventId: UInt64, newOwner: Address?, serial: UInt64)
49
+ pub event FLOATPurchased(id: UInt64, eventHost: Address, eventId: UInt64, recipient: Address, serial: UInt64)
50
+ pub event FLOATEventCreated(eventId: UInt64, description: String, host: Address, image: String, name: String, url: String)
51
+ pub event FLOATEventDestroyed(eventId: UInt64, host: Address, name: String)
52
+
53
+ pub event Deposit(id: UInt64, to: Address?)
54
+ pub event Withdraw(id: UInt64, from: Address?)
55
+
56
+ /***********************************************/
57
+ /******************** STATE ********************/
58
+ /***********************************************/
59
+
60
+ // The total amount of FLOATs that have ever been
61
+ // created (does not go down when a FLOAT is destroyed)
62
+ pub var totalSupply: UInt64
63
+ // The total amount of FLOATEvents that have ever been
64
+ // created (does not go down when a FLOATEvent is destroyed)
65
+ pub var totalFLOATEvents: UInt64
66
+
67
+ /***********************************************/
68
+ /**************** FUNCTIONALITY ****************/
69
+ /***********************************************/
70
+
71
+ // A helpful wrapper to contain an address,
72
+ // the id of a FLOAT, and its serial
73
+ pub struct TokenIdentifier {
74
+ pub let id: UInt64
75
+ pub let address: Address
76
+ pub let serial: UInt64
77
+
78
+ init(_id: UInt64, _address: Address, _serial: UInt64) {
79
+ self.id = _id
80
+ self.address = _address
81
+ self.serial = _serial
82
+ }
83
+ }
84
+
85
+ pub struct TokenInfo {
86
+ pub let path: PublicPath
87
+ pub let price: UFix64
88
+
89
+ init(_path: PublicPath, _price: UFix64) {
90
+ self.path = _path
91
+ self.price = _price
92
+ }
93
+ }
94
+
95
+ // Represents a FLOAT
96
+ pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
97
+ // The `uuid` of this resource
98
+ pub let id: UInt64
99
+
100
+ // Some of these are also duplicated on the event,
101
+ // but it's necessary to put them here as well
102
+ // in case the FLOATEvent host deletes the event
103
+ pub let dateReceived: UFix64
104
+ pub let eventDescription: String
105
+ pub let eventHost: Address
106
+ pub let eventId: UInt64
107
+ pub let eventImage: String
108
+ pub let eventName: String
109
+ pub let originalRecipient: Address
110
+ pub let serial: UInt64
111
+
112
+ // A capability that points to the FLOATEvents this FLOAT is from.
113
+ // There is a chance the event host unlinks their event from
114
+ // the public, in which case it's impossible to know details
115
+ // about the event. Which is fine, since we store the
116
+ // crucial data to know about the FLOAT in the FLOAT itself.
117
+ pub let eventsCap: Capability<&FLOATEvents{FLOATEventsPublic, MetadataViews.ResolverCollection}>
118
+
119
+ // Helper function to get the metadata of the event
120
+ // this FLOAT is from.
121
+ pub fun getEventRef(): &FLOATEvent{FLOATEventPublic}? {
122
+ if let events: &FLOATEvents{FLOATEventsPublic, MetadataViews.ResolverCollection} = self.eventsCap.borrow() {
123
+ return events.borrowPublicEventRef(eventId: self.eventId)
124
+ }
125
+ return nil
126
+ }
127
+
128
+ pub fun getExtraMetadata(): {String: AnyStruct} {
129
+ if let event: &FLOATEvent{FLOATEventPublic} = self.getEventRef() {
130
+ return event.getExtraFloatMetadata(serial: self.serial)
131
+ }
132
+ return {}
133
+ }
134
+
135
+ pub fun getSpecificExtraMetadata(key: String): AnyStruct? {
136
+ return self.getExtraMetadata()[key]
137
+ }
138
+
139
+ pub fun getImage(): String {
140
+ if let extraEventMetadata: {String: AnyStruct} = self.getEventRef()?.getExtraMetadata() {
141
+ if FLOAT.extraMetadataToStrOpt(extraEventMetadata, "visibilityMode") == "picture" {
142
+ return self.eventImage
143
+ }
144
+ if let certificateType: String = FLOAT.extraMetadataToStrOpt(extraEventMetadata, "certificateType") {
145
+ if certificateType == "medal" {
146
+ // Extra metadata about medal colors
147
+ if let medalType: String = FLOAT.extraMetadataToStrOpt(self.getExtraMetadata(), "medalType") {
148
+ return FLOAT.extraMetadataToStrOpt(extraEventMetadata, "certificateImage.".concat(medalType)) ?? self.eventImage
149
+ }
150
+ // if there is no medal type for the FLOAT
151
+ return FLOAT.extraMetadataToStrOpt(extraEventMetadata, "certificateImage.participation") ?? self.eventImage
152
+ }
153
+ return FLOAT.extraMetadataToStrOpt(extraEventMetadata, "certificateImage") ?? self.eventImage
154
+ }
155
+ }
156
+ return self.eventImage
157
+ }
158
+
159
+ // This is for the MetdataStandard
160
+ pub fun getViews(): [Type] {
161
+ let supportedViews = [
162
+ Type<MetadataViews.Display>(),
163
+ Type<MetadataViews.Royalties>(),
164
+ Type<MetadataViews.ExternalURL>(),
165
+ Type<MetadataViews.NFTCollectionData>(),
166
+ Type<MetadataViews.NFTCollectionDisplay>(),
167
+ Type<MetadataViews.Traits>(),
168
+ Type<MetadataViews.Serial>(),
169
+ Type<TokenIdentifier>()
170
+ ]
171
+
172
+ if self.getEventRef()?.transferrable == false {
173
+ supportedViews.append(Type<FindViews.SoulBound>())
174
+ }
175
+
176
+ return supportedViews
177
+ }
178
+
179
+ // This is for the MetdataStandard
180
+ pub fun resolveView(_ view: Type): AnyStruct? {
181
+ switch view {
182
+ case Type<MetadataViews.Display>():
183
+ return MetadataViews.Display(
184
+ name: self.eventName,
185
+ description: self.eventDescription,
186
+ thumbnail: MetadataViews.HTTPFile(url: "https://nftstorage.link/ipfs/".concat(self.getImage()))
187
+ )
188
+ case Type<MetadataViews.Royalties>():
189
+ return MetadataViews.Royalties([
190
+ MetadataViews.Royalty(
191
+ recepient: getAccount(0x5643fd47a29770e7).getCapability<&FlowToken.Vault{FungibleToken.Receiver}>(/public/flowTokenReceiver),
192
+ cut: 0.05, // 5% royalty on secondary sales
193
+ description: "Emerald City DAO receives a 5% royalty from secondary sales because this NFT was created using FLOAT (https://floats.city/), a proof of attendance platform created by Emerald City DAO."
194
+ )
195
+ ])
196
+ case Type<MetadataViews.ExternalURL>():
197
+ return MetadataViews.ExternalURL("https://floats.city/".concat(self.owner!.address.toString()).concat("/float/").concat(self.id.toString()))
198
+ case Type<MetadataViews.NFTCollectionData>():
199
+ return FLOAT.resolveView(view)
200
+ case Type<MetadataViews.NFTCollectionDisplay>():
201
+ return FLOAT.resolveView(view)
202
+ case Type<MetadataViews.Serial>():
203
+ return MetadataViews.Serial(
204
+ self.serial
205
+ )
206
+ case Type<TokenIdentifier>():
207
+ return TokenIdentifier(
208
+ _id: self.id,
209
+ _address: self.owner!.address,
210
+ _serial: self.serial
211
+ )
212
+ case Type<FindViews.SoulBound>():
213
+ if self.getEventRef()?.transferrable == false {
214
+ return FindViews.SoulBound(
215
+ "This FLOAT is soulbound because the event host toggled off transferring."
216
+ )
217
+ }
218
+ return nil
219
+ case Type<MetadataViews.Traits>():
220
+ let traitsView: MetadataViews.Traits = MetadataViews.dictToTraits(dict: self.getExtraMetadata(), excludedNames: nil)
221
+
222
+ if let eventRef: &FLOATEvent{FLOATEventPublic} = self.getEventRef() {
223
+ let eventExtraMetadata: {String: AnyStruct} = eventRef.getExtraMetadata()
224
+
225
+ let certificateType: MetadataViews.Trait = MetadataViews.Trait(name: "certificateType", value: eventExtraMetadata["certificateType"], displayType: nil, rarity: nil)
226
+ traitsView.addTrait(certificateType)
227
+
228
+ let eventType: MetadataViews.Trait = MetadataViews.Trait(name: "eventType", value: eventExtraMetadata["eventType"], displayType: nil, rarity: nil)
229
+ traitsView.addTrait(eventType)
230
+ }
231
+
232
+ return traitsView
233
+ }
234
+
235
+ return nil
236
+ }
237
+
238
+ init(_eventDescription: String, _eventHost: Address, _eventId: UInt64, _eventImage: String, _eventName: String, _originalRecipient: Address, _serial: UInt64) {
239
+ self.id = self.uuid
240
+ self.dateReceived = getCurrentBlock().timestamp
241
+ self.eventDescription = _eventDescription
242
+ self.eventHost = _eventHost
243
+ self.eventId = _eventId
244
+ self.eventImage = _eventImage
245
+ self.eventName = _eventName
246
+ self.originalRecipient = _originalRecipient
247
+ self.serial = _serial
248
+
249
+ // Stores a capability to the FLOATEvents of its creator
250
+ self.eventsCap = getAccount(_eventHost)
251
+ .getCapability<&FLOATEvents{FLOATEventsPublic, MetadataViews.ResolverCollection}>(FLOAT.FLOATEventsPublicPath)
252
+
253
+ emit FLOATMinted(
254
+ id: self.id,
255
+ eventHost: _eventHost,
256
+ eventId: _eventId,
257
+ eventImage: _eventImage,
258
+ recipient: _originalRecipient,
259
+ serial: _serial
260
+ )
261
+
262
+ FLOAT.totalSupply = FLOAT.totalSupply + 1
263
+ }
264
+
265
+ destroy() {
266
+ emit FLOATDestroyed(
267
+ id: self.id,
268
+ eventHost: self.eventHost,
269
+ eventId: self.eventId,
270
+ eventImage: self.eventImage,
271
+ serial: self.serial
272
+ )
273
+ }
274
+ }
275
+
276
+ // A public interface for people to call into our Collection
277
+ pub resource interface CollectionPublic {
278
+ pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
279
+ pub fun borrowFLOAT(id: UInt64): &NFT?
280
+ pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver}
281
+ pub fun deposit(token: @NonFungibleToken.NFT)
282
+ pub fun getIDs(): [UInt64]
283
+ pub fun getAllIDs(): [UInt64]
284
+ pub fun ownedIdsFromEvent(eventId: UInt64): [UInt64]
285
+ }
286
+
287
+ // A Collection that holds all of the users FLOATs.
288
+ // Withdrawing is not allowed. You can only transfer.
289
+ pub resource Collection: NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection, CollectionPublic {
290
+ // Maps a FLOAT id to the FLOAT itself
291
+ pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
292
+ // Maps an eventId to the ids of FLOATs that
293
+ // this user owns from that event. It's possible
294
+ // for it to be out of sync until June 2022 spork,
295
+ // but it is used merely as a helper, so that's okay.
296
+ access(self) var events: {UInt64: {UInt64: Bool}}
297
+
298
+ // Deposits a FLOAT to the collection
299
+ pub fun deposit(token: @NonFungibleToken.NFT) {
300
+ let nft <- token as! @NFT
301
+ let id = nft.id
302
+ let eventId = nft.eventId
303
+
304
+ // Update self.events[eventId] to have
305
+ // this FLOAT's id in it
306
+ if self.events[eventId] == nil {
307
+ self.events[eventId] = {id: true}
308
+ } else {
309
+ self.events[eventId]!.insert(key: id, true)
310
+ }
311
+
312
+ emit Deposit(id: id, to: self.owner!.address)
313
+ emit FLOATTransferred(id: id, eventHost: nft.eventHost, eventId: nft.eventId, newOwner: self.owner!.address, serial: nft.serial)
314
+ self.ownedNFTs[id] <-! nft
315
+ }
316
+
317
+ pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
318
+ let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("You do not own this FLOAT in your collection")
319
+ let nft <- token as! @NFT
320
+
321
+ // Update self.events[eventId] to not
322
+ // have this FLOAT's id in it
323
+ self.events[nft.eventId]!.remove(key: withdrawID)
324
+
325
+ // This checks if the FLOATEvent host wanted this
326
+ // FLOAT to be transferrable. Secondary marketplaces will use this
327
+ // withdraw function, so if the FLOAT is not transferrable,
328
+ // you can't sell it there.
329
+ if let floatEvent: &FLOATEvent{FLOATEventPublic} = nft.getEventRef() {
330
+ assert(
331
+ floatEvent.transferrable,
332
+ message: "This FLOAT is not transferrable."
333
+ )
334
+ }
335
+
336
+ emit Withdraw(id: withdrawID, from: self.owner!.address)
337
+ emit FLOATTransferred(id: withdrawID, eventHost: nft.eventHost, eventId: nft.eventId, newOwner: nil, serial: nft.serial)
338
+ return <- nft
339
+ }
340
+
341
+ pub fun delete(id: UInt64) {
342
+ let token <- self.ownedNFTs.remove(key: id) ?? panic("You do not own this FLOAT in your collection")
343
+ let nft <- token as! @NFT
344
+
345
+ // Update self.events[eventId] to not
346
+ // have this FLOAT's id in it
347
+ self.events[nft.eventId]!.remove(key: id)
348
+
349
+ destroy nft
350
+ }
351
+
352
+ // Only returns the FLOATs for which we can still
353
+ // access data about their event.
354
+ pub fun getIDs(): [UInt64] {
355
+ let ids: [UInt64] = []
356
+ for key in self.ownedNFTs.keys {
357
+ let nftRef = self.borrowFLOAT(id: key)!
358
+ if nftRef.eventsCap.check() {
359
+ ids.append(key)
360
+ }
361
+ }
362
+ return ids
363
+ }
364
+
365
+ // Returns all the FLOATs ids
366
+ pub fun getAllIDs(): [UInt64] {
367
+ return self.ownedNFTs.keys
368
+ }
369
+
370
+ // Returns an array of ids that belong to
371
+ // the passed in eventId
372
+ //
373
+ // It's possible for FLOAT ids to be present that
374
+ // shouldn't be if people tried to withdraw directly
375
+ // from `ownedNFTs` (not possible after June 2022 spork),
376
+ // but this makes sure the returned
377
+ // ids are all actually owned by this account.
378
+ pub fun ownedIdsFromEvent(eventId: UInt64): [UInt64] {
379
+ let answer: [UInt64] = []
380
+ if let idsInEvent = self.events[eventId]?.keys {
381
+ for id in idsInEvent {
382
+ if self.ownedNFTs[id] != nil {
383
+ answer.append(id)
384
+ }
385
+ }
386
+ }
387
+ return answer
388
+ }
389
+
390
+ pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
391
+ return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
392
+ }
393
+
394
+ pub fun borrowFLOAT(id: UInt64): &NFT? {
395
+ if self.ownedNFTs[id] != nil {
396
+ let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
397
+ return ref as! &NFT
398
+ }
399
+ return nil
400
+ }
401
+
402
+ pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver} {
403
+ let tokenRef = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
404
+ let nftRef = tokenRef as! &NFT
405
+ return nftRef as &{MetadataViews.Resolver}
406
+ }
407
+
408
+ init() {
409
+ self.ownedNFTs <- {}
410
+ self.events = {}
411
+ }
412
+
413
+ destroy() {
414
+ destroy self.ownedNFTs
415
+ }
416
+ }
417
+
418
+ // An interface that every "verifier" must implement.
419
+ // A verifier is one of the options on the FLOAT Event page,
420
+ // for example, a "time limit," or a "limited" number of
421
+ // FLOATs that can be claimed.
422
+ // All the current verifiers can be seen inside FLOATVerifiers.cdc
423
+ pub struct interface IVerifier {
424
+ // A function every verifier must implement.
425
+ // Will have `assert`s in it to make sure
426
+ // the user fits some criteria.
427
+ access(account) fun verify(_ params: {String: AnyStruct})
428
+ }
429
+
430
+ // A public interface to read the FLOATEvent
431
+ pub resource interface FLOATEventPublic {
432
+ pub var claimable: Bool
433
+ pub let dateCreated: UFix64
434
+ pub let description: String
435
+ pub let eventId: UInt64
436
+ pub let host: Address
437
+ pub let image: String
438
+ pub let name: String
439
+ pub var totalSupply: UInt64
440
+ pub var transferrable: Bool
441
+ pub let url: String
442
+
443
+ pub fun claim(recipient: &Collection, params: {String: AnyStruct})
444
+ pub fun purchase(recipient: &Collection, params: {String: AnyStruct}, payment: @FungibleToken.Vault)
445
+
446
+ pub fun getExtraMetadata(): {String: AnyStruct}
447
+ pub fun getSpecificExtraMetadata(key: String): AnyStruct?
448
+ pub fun getVerifiers(): {String: [{IVerifier}]}
449
+ pub fun getPrices(): {String: TokenInfo}?
450
+ pub fun getExtraFloatMetadata(serial: UInt64): {String: AnyStruct}
451
+ pub fun getSpecificExtraFloatMetadata(serial: UInt64, key: String): AnyStruct?
452
+ pub fun getClaims(): {UInt64: TokenIdentifier}
453
+ pub fun getSerialsUserClaimed(address: Address): [UInt64]
454
+ pub fun userHasClaimed(address: Address): Bool
455
+ pub fun userCanMint(address: Address): Bool
456
+ }
457
+
458
+ //
459
+ // FLOATEvent
460
+ //
461
+ pub resource FLOATEvent: FLOATEventPublic, MetadataViews.Resolver {
462
+ // Whether or not users can claim from our event (can be toggled
463
+ // at any time)
464
+ pub var claimable: Bool
465
+ pub let dateCreated: UFix64
466
+ pub let description: String
467
+ // This is equal to this resource's uuid
468
+ pub let eventId: UInt64
469
+ // Who created this FLOAT Event
470
+ pub let host: Address
471
+ // The image of the FLOAT Event
472
+ pub let image: String
473
+ // The name of the FLOAT Event
474
+ pub let name: String
475
+ // The total number of FLOATs that have been
476
+ // minted from this event
477
+ pub var totalSupply: UInt64
478
+ // Whether or not the FLOATs that users own
479
+ // from this event can be transferred on the
480
+ // FLOAT platform itself (transferring allowed
481
+ // elsewhere)
482
+ pub var transferrable: Bool
483
+ // A url of where the event took place
484
+ pub let url: String
485
+ // A list of verifiers this FLOAT Event contains.
486
+ // Will be used every time someone "claims" a FLOAT
487
+ // to see if they pass the requirements
488
+ access(self) let verifiers: {String: [{IVerifier}]}
489
+ // Used to store extra metadata about the event but
490
+ // also individual FLOATs, because Jacob forgot to
491
+ // put a dictionary in the NFT resource :/ Idiot
492
+ access(self) var extraMetadata: {String: AnyStruct}
493
+
494
+ // DEPRECATED, DO NOT USE
495
+ access(self) var claimed: {Address: TokenIdentifier}
496
+ // DEPRECATED, DO NOT USE (used for storing claim info now)
497
+ access(self) var currentHolders: {UInt64: TokenIdentifier}
498
+ // DEPRECATED, DO NOT USE
499
+ access(self) var groups: {String: Bool}
500
+
501
+ // Type: Admin Toggle
502
+ pub fun toggleClaimable(): Bool {
503
+ self.claimable = !self.claimable
504
+ return self.claimable
505
+ }
506
+
507
+ // Type: Admin Toggle
508
+ pub fun toggleTransferrable(): Bool {
509
+ self.transferrable = !self.transferrable
510
+ return self.transferrable
511
+ }
512
+
513
+ // Type: Admin Toggle
514
+ pub fun toggleVisibilityMode() {
515
+ if let currentVisibilityMode: String = FLOAT.extraMetadataToStrOpt(self.getExtraMetadata(), "visibilityMode") {
516
+ if currentVisibilityMode == "certificate" {
517
+ self.extraMetadata["visibilityMode"] = "picture"
518
+ return
519
+ }
520
+ }
521
+ self.extraMetadata["visibilityMode"] = "certificate"
522
+ }
523
+
524
+ // Type: Contract Setter
525
+ access(self) fun setUserClaim(serial: UInt64, address: Address, floatId: UInt64) {
526
+ self.currentHolders[serial] = TokenIdentifier(_id: floatId, _address: address, _serial: serial)
527
+
528
+ if self.extraMetadata["userClaims"] == nil {
529
+ let userClaims: {Address: [UInt64]} = {}
530
+ self.extraMetadata["userClaims"] = userClaims
531
+ }
532
+ let e = (&self.extraMetadata["userClaims"] as auth &AnyStruct?)!
533
+ let claims = e as! &{Address: [UInt64]}
534
+
535
+ if let specificUserClaims: [UInt64] = claims[address] {
536
+ claims[address]!.append(serial)
537
+ } else {
538
+ claims[address] = [serial]
539
+ }
540
+ }
541
+
542
+ // Type: Contract Setter
543
+ access(self) fun setExtraFloatMetadata(serial: UInt64, metadata: {String: AnyStruct}) {
544
+ if self.extraMetadata["extraFloatMetadatas"] == nil {
545
+ let extraFloatMetadatas: {UInt64: AnyStruct} = {}
546
+ self.extraMetadata["extraFloatMetadatas"] = extraFloatMetadatas
547
+ }
548
+ let e = (&self.extraMetadata["extraFloatMetadatas"] as auth &AnyStruct?)!
549
+ let extraFloatMetadatas = e as! &{UInt64: AnyStruct}
550
+ extraFloatMetadatas[serial] = metadata
551
+ }
552
+
553
+ // Type: Contract Setter
554
+ access(self) fun setSpecificExtraFloatMetadata(serial: UInt64, key: String, value: AnyStruct) {
555
+ if self.extraMetadata["extraFloatMetadatas"] == nil {
556
+ let extraFloatMetadatas: {UInt64: AnyStruct} = {}
557
+ self.extraMetadata["extraFloatMetadatas"] = extraFloatMetadatas
558
+ }
559
+ let e = (&self.extraMetadata["extraFloatMetadatas"] as auth &AnyStruct?)!
560
+ let extraFloatMetadatas = e as! &{UInt64: AnyStruct}
561
+
562
+ if extraFloatMetadatas[serial] == nil {
563
+ let extraFloatMetadata: {String: AnyStruct} = {}
564
+ extraFloatMetadatas[serial] = extraFloatMetadata
565
+ }
566
+
567
+ let f = (&extraFloatMetadatas[serial] as auth &AnyStruct?)!
568
+ let extraFloatMetadata = e as! &{String: AnyStruct}
569
+ extraFloatMetadata[key] = value
570
+ }
571
+
572
+ // Type: Getter
573
+ // Description: Get extra metadata on a specific FLOAT from this event
574
+ pub fun getExtraFloatMetadata(serial: UInt64): {String: AnyStruct} {
575
+ if self.extraMetadata["extraFloatMetadatas"] != nil {
576
+ if let e: {UInt64: AnyStruct} = self.extraMetadata["extraFloatMetadatas"]! as? {UInt64: AnyStruct} {
577
+ if e[serial] != nil {
578
+ if let f: {String: AnyStruct} = e[serial]! as? {String: AnyStruct} {
579
+ return f
580
+ }
581
+ }
582
+ }
583
+ }
584
+ return {}
585
+ }
586
+
587
+ // Type: Getter
588
+ // Description: Get specific extra metadata on a specific FLOAT from this event
589
+ pub fun getSpecificExtraFloatMetadata(serial: UInt64, key: String): AnyStruct? {
590
+ return self.getExtraFloatMetadata(serial: serial)[key]
591
+ }
592
+
593
+ // Type: Getter
594
+ // Description: Returns claim info of all the serials
595
+ pub fun getClaims(): {UInt64: TokenIdentifier} {
596
+ return self.currentHolders
597
+ }
598
+
599
+ // Type: Getter
600
+ // Description: Will return an array of all the serials a user claimed.
601
+ // Most of the time this will be a maximum length of 1 because most
602
+ // events only allow 1 claim per user.
603
+ pub fun getSerialsUserClaimed(address: Address): [UInt64] {
604
+ var serials: [UInt64] = []
605
+ if let userClaims: {Address: [UInt64]} = self.getSpecificExtraMetadata(key: "userClaims") as! {Address: [UInt64]}? {
606
+ serials = userClaims[address] ?? []
607
+ }
608
+ // take into account claims during FLOATv1
609
+ if let oldClaim: TokenIdentifier = self.claimed[address] {
610
+ serials.append(oldClaim.serial)
611
+ }
612
+ return serials
613
+ }
614
+
615
+ // Type: Getter
616
+ // Description: Returns true if the user has either claimed
617
+ // or been minted at least one float from this event
618
+ pub fun userHasClaimed(address: Address): Bool {
619
+ return self.getSerialsUserClaimed(address: address).length >= 1
620
+ }
621
+
622
+ // Type: Getter
623
+ // Description: Get extra metadata on this event
624
+ pub fun getExtraMetadata(): {String: AnyStruct} {
625
+ return self.extraMetadata
626
+ }
627
+
628
+ // Type: Getter
629
+ // Description: Get specific extra metadata on this event
630
+ pub fun getSpecificExtraMetadata(key: String): AnyStruct? {
631
+ return self.extraMetadata[key]
632
+ }
633
+
634
+ // Type: Getter
635
+ // Description: Checks if a user can mint a new FLOAT from this event
636
+ pub fun userCanMint(address: Address): Bool {
637
+ if let allows: Bool = self.getSpecificExtraMetadata(key: "allowMultipleClaim") as! Bool? {
638
+ if allows || self.getSerialsUserClaimed(address: address).length == 0 {
639
+ return true
640
+ }
641
+ }
642
+ return false
643
+ }
644
+
645
+ // Type: Getter
646
+ // Description: Gets all the verifiers that will be used
647
+ // for claiming
648
+ pub fun getVerifiers(): {String: [{IVerifier}]} {
649
+ return self.verifiers
650
+ }
651
+
652
+ // Type: Getter
653
+ // Description: Returns a dictionary whose key is a token identifier
654
+ // and value is the path to that token and price of the FLOAT in that
655
+ // currency
656
+ pub fun getPrices(): {String: TokenInfo}? {
657
+ return self.extraMetadata["prices"] as! {String: TokenInfo}?
658
+ }
659
+
660
+ // Type: Getter
661
+ // Description: For MetadataViews
662
+ pub fun getViews(): [Type] {
663
+ return [
664
+ Type<MetadataViews.Display>()
665
+ ]
666
+ }
667
+
668
+ // Type: Getter
669
+ // Description: For MetadataViews
670
+ pub fun resolveView(_ view: Type): AnyStruct? {
671
+ switch view {
672
+ case Type<MetadataViews.Display>():
673
+ return MetadataViews.Display(
674
+ name: self.name,
675
+ description: self.description,
676
+ thumbnail: MetadataViews.IPFSFile(cid: self.image, path: nil)
677
+ )
678
+ }
679
+
680
+ return nil
681
+ }
682
+
683
+ // Type: Admin / Helper Function for `verifyAndMint`
684
+ // Description: Used to give a person a FLOAT from this event.
685
+ // If the event owner directly mints to a user, it does not
686
+ // run the verifiers on the user. It bypasses all of them.
687
+ // Return the id of the FLOAT it minted.
688
+ pub fun mint(recipient: &Collection{NonFungibleToken.CollectionPublic}, optExtraFloatMetadata: {String: AnyStruct}?): UInt64 {
689
+ pre {
690
+ self.userCanMint(address: recipient.owner!.address): "Only 1 FLOAT allowed per user, and this user already claimed their FLOAT!"
691
+ }
692
+ let recipientAddr: Address = recipient.owner!.address
693
+ let serial = self.totalSupply
694
+
695
+ let token <- create NFT(
696
+ _eventDescription: self.description,
697
+ _eventHost: self.host,
698
+ _eventId: self.eventId,
699
+ _eventImage: self.image,
700
+ _eventName: self.name,
701
+ _originalRecipient: recipientAddr,
702
+ _serial: serial
703
+ )
704
+
705
+ if let extraFloatMetadata: {String: AnyStruct} = optExtraFloatMetadata {
706
+ // ensure
707
+ assert(
708
+ FLOAT.validateExtraFloatMetadata(data: extraFloatMetadata),
709
+ message: "Extra FLOAT metadata is not proper. Check the `FLOAT.validateExtraFloatMetadata` function."
710
+ )
711
+ self.setExtraFloatMetadata(serial: serial, metadata: extraFloatMetadata)
712
+ }
713
+
714
+ let id: UInt64 = token.id
715
+
716
+ self.setUserClaim(serial: serial, address: recipientAddr, floatId: id)
717
+
718
+ self.totalSupply = self.totalSupply + 1
719
+ recipient.deposit(token: <- token)
720
+ return id
721
+ }
722
+
723
+ // Type: Helper Function for `claim` and `purchase`
724
+ // Description: Will get run by the public, so verifies
725
+ // the user can mint
726
+ access(self) fun verifyAndMint(recipient: &Collection, params: {String: AnyStruct}): UInt64 {
727
+ params["event"] = &self as &FLOATEvent{FLOATEventPublic}
728
+ params["claimee"] = recipient.owner!.address
729
+
730
+ // Runs a loop over all the verifiers that this FLOAT Events
731
+ // implements. For example, "Limited", "Timelock", "Secret", etc.
732
+ // All the verifiers are in the FLOATVerifiers.cdc contract
733
+ for identifier in self.verifiers.keys {
734
+ let typedModules = (&self.verifiers[identifier] as &[{IVerifier}]?)!
735
+ var i = 0
736
+ while i < typedModules.length {
737
+ let verifier = &typedModules[i] as &{IVerifier}
738
+ verifier.verify(params)
739
+ i = i + 1
740
+ }
741
+ }
742
+
743
+ var optExtraFloatMetadata: {String: AnyStruct}? = nil
744
+ // if this is a medal type float and user is publicly claiming, assign to participation
745
+ if FLOAT.extraMetadataToStrOpt(self.getExtraMetadata(), "certificateType") == "medal" {
746
+ optExtraFloatMetadata = {"medalType": "participation"}
747
+ }
748
+
749
+ // You're good to go.
750
+ let id: UInt64 = self.mint(recipient: recipient, optExtraFloatMetadata: optExtraFloatMetadata)
751
+
752
+ emit FLOATClaimed(
753
+ id: id,
754
+ eventHost: self.host,
755
+ eventId: self.eventId,
756
+ eventImage: self.image,
757
+ eventName: self.name,
758
+ recipient: recipient.owner!.address,
759
+ serial: self.totalSupply - 1
760
+ )
761
+ return id
762
+ }
763
+
764
+ // For the public to claim FLOATs. Must be claimable to do so.
765
+ // You can pass in `params` that will be forwarded to the
766
+ // customized `verify` function of the verifier.
767
+ //
768
+ // For example, the FLOAT platform allows event hosts
769
+ // to specify a secret phrase. That secret phrase will
770
+ // be passed in the `params`.
771
+ pub fun claim(recipient: &Collection, params: {String: AnyStruct}) {
772
+ pre {
773
+ self.getPrices() == nil:
774
+ "You need to purchase this FLOAT."
775
+ self.claimable:
776
+ "This FLOAT event is not claimable, and thus not currently active."
777
+ }
778
+
779
+ self.verifyAndMint(recipient: recipient, params: params)
780
+ }
781
+
782
+ pub fun purchase(recipient: &Collection, params: {String: AnyStruct}, payment: @FungibleToken.Vault) {
783
+ pre {
784
+ self.getPrices() != nil:
785
+ "Don't call this function. The FLOAT is free. Call the claim function instead."
786
+ self.getPrices()![payment.getType().identifier] != nil:
787
+ "This FLOAT does not support purchasing in the passed in token."
788
+ payment.balance == self.getPrices()![payment.getType().identifier]!.price:
789
+ "You did not pass in the correct amount of tokens."
790
+ self.claimable:
791
+ "This FLOAT event is not claimable, and thus not currently active."
792
+ }
793
+ let royalty: UFix64 = 0.05
794
+ let emeraldCityTreasury: Address = 0x5643fd47a29770e7
795
+ let paymentType: String = payment.getType().identifier
796
+ let tokenInfo: TokenInfo = self.getPrices()![paymentType]!
797
+
798
+ let EventHostVault = getAccount(self.host).getCapability(tokenInfo.path)
799
+ .borrow<&{FungibleToken.Receiver}>()
800
+ ?? panic("Could not borrow the &{FungibleToken.Receiver} from the event host.")
801
+
802
+ assert(
803
+ EventHostVault.getType().identifier == paymentType,
804
+ message: "The event host's path is not associated with the intended token."
805
+ )
806
+
807
+ let EmeraldCityVault = getAccount(emeraldCityTreasury).getCapability(tokenInfo.path)
808
+ .borrow<&{FungibleToken.Receiver}>()
809
+ ?? panic("Could not borrow the &{FungibleToken.Receiver} from Emerald City's Vault.")
810
+
811
+ assert(
812
+ EmeraldCityVault.getType().identifier == paymentType,
813
+ message: "Emerald City's path is not associated with the intended token."
814
+ )
815
+
816
+ let emeraldCityCut <- payment.withdraw(amount: payment.balance * royalty)
817
+
818
+ EmeraldCityVault.deposit(from: <- emeraldCityCut)
819
+ EventHostVault.deposit(from: <- payment)
820
+
821
+ let id = self.verifyAndMint(recipient: recipient, params: params)
822
+
823
+ emit FLOATPurchased(id: id, eventHost: self.host, eventId: self.eventId, recipient: recipient.owner!.address, serial: self.totalSupply - 1)
824
+ }
825
+
826
+ init (
827
+ _claimable: Bool,
828
+ _description: String,
829
+ _extraMetadata: {String: AnyStruct},
830
+ _host: Address,
831
+ _image: String,
832
+ _name: String,
833
+ _transferrable: Bool,
834
+ _url: String,
835
+ _verifiers: {String: [{IVerifier}]}
836
+ ) {
837
+ self.claimable = _claimable
838
+ self.claimed = {}
839
+ self.currentHolders = {}
840
+ self.dateCreated = getCurrentBlock().timestamp
841
+ self.description = _description
842
+ self.eventId = self.uuid
843
+ self.extraMetadata = _extraMetadata
844
+ self.host = _host
845
+ self.image = _image
846
+ self.name = _name
847
+ self.transferrable = _transferrable
848
+ self.totalSupply = 0
849
+ self.url = _url
850
+ self.verifiers = _verifiers
851
+ self.groups = {}
852
+
853
+ FLOAT.totalFLOATEvents = FLOAT.totalFLOATEvents + 1
854
+ emit FLOATEventCreated(eventId: self.eventId, description: self.description, host: self.host, image: self.image, name: self.name, url: self.url)
855
+ }
856
+
857
+ destroy() {
858
+ emit FLOATEventDestroyed(eventId: self.eventId, host: self.host, name: self.name)
859
+ }
860
+ }
861
+
862
+ // DEPRECATED
863
+ pub resource Group {
864
+ pub let id: UInt64
865
+ pub let name: String
866
+ pub let image: String
867
+ pub let description: String
868
+ access(self) var events: {UInt64: Bool}
869
+ init() {
870
+ self.id = 0
871
+ self.name = ""
872
+ self.image = ""
873
+ self.description = ""
874
+ self.events = {}
875
+ }
876
+ }
877
+
878
+ //
879
+ // FLOATEvents
880
+ //
881
+ pub resource interface FLOATEventsPublic {
882
+ // Public Getters
883
+ pub fun borrowPublicEventRef(eventId: UInt64): &FLOATEvent{FLOATEventPublic}?
884
+ pub fun getAllEvents(): {UInt64: String}
885
+ pub fun getIDs(): [UInt64]
886
+ pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver}
887
+ }
888
+
889
+ // A "Collection" of FLOAT Events
890
+ pub resource FLOATEvents: FLOATEventsPublic, MetadataViews.ResolverCollection {
891
+ // All the FLOAT Events this collection stores
892
+ access(self) var events: @{UInt64: FLOATEvent}
893
+ // DEPRECATED
894
+ access(self) var groups: @{String: Group}
895
+
896
+ // Creates a new FLOAT Event
897
+ //
898
+ // Read below for a description on all the values and expectations here
899
+ //
900
+ // claimable: Do you want this FLOAT to be publicly claimable by users?
901
+ // transferrable: Should this FLOAT be transferrable or soulbound?
902
+ // url: A generic url to your FLOAT Event
903
+ // verifiers: An array of verifiers from FLOATVerifiers contract
904
+ // allowMultipleClaim: Should users be able to claim/receive multiple
905
+ // of this FLOAT?
906
+ // certificateType: Determines how the FLOAT is displayed on the FLOAT platform. Must be one of the
907
+ // following or it will fail: "ticket", "medal", "certificate"
908
+ // visibilityMode: Determines how the FLOAT is displayed on the FLOAT platform. Must be one of the
909
+ // following: "picture", "certificate"
910
+ // extraMetadata: Any extra metadata for your event. Here are some restrictions on the keys of this dictionary:
911
+ // userClaims: You cannot provide a userClaims key
912
+ // extraFloatMetadatas: You cannot provide a extraFloatMetadatas key
913
+ // certificateImage: Must either be nil or a String type
914
+ // backImage: The IPFS CID of what will display on the back of your FLOAT. Must either be nil or a String type
915
+ // eventType: Must either be nil or a String type
916
+ pub fun createEvent(
917
+ claimable: Bool,
918
+ description: String,
919
+ image: String,
920
+ name: String,
921
+ transferrable: Bool,
922
+ url: String,
923
+ verifiers: [{IVerifier}],
924
+ allowMultipleClaim: Bool,
925
+ certificateType: String,
926
+ visibilityMode: String,
927
+ extraMetadata: {String: AnyStruct}
928
+ ): UInt64 {
929
+ pre {
930
+ certificateType == "ticket" || certificateType == "medal" || certificateType == "certificate": "You must either choose 'ticket', 'medal', or 'certificate' for certificateType. This is how your FLOAT will be displayed."
931
+ visibilityMode == "certificate" || visibilityMode == "picture": "You must either choose 'certificate' or 'picture' for visibilityMode. This is how your FLOAT will be displayed."
932
+ extraMetadata["userClaims"] == nil: "Cannot use userClaims key in extraMetadata."
933
+ extraMetadata["extraFloatMetadatas"] == nil: "Cannot use extraFloatMetadatas key in extraMetadata."
934
+ extraMetadata["certificateImage"] == nil || extraMetadata["certificateImage"]!.getType() == Type<String>(): "certificateImage must be a String or nil type."
935
+ extraMetadata["backImage"] == nil || extraMetadata["backImage"]!.getType() == Type<String>(): "backImage must be a String or nil type."
936
+ extraMetadata["eventType"] == nil || extraMetadata["eventType"]!.getType() == Type<String>(): "eventType must be a String or nil type."
937
+ }
938
+
939
+ let typedVerifiers: {String: [{IVerifier}]} = {}
940
+ for verifier in verifiers {
941
+ let identifier = verifier.getType().identifier
942
+ if typedVerifiers[identifier] == nil {
943
+ typedVerifiers[identifier] = [verifier]
944
+ } else {
945
+ typedVerifiers[identifier]!.append(verifier)
946
+ }
947
+ }
948
+
949
+ extraMetadata["allowMultipleClaim"] = allowMultipleClaim
950
+ extraMetadata["certificateType"] = certificateType
951
+ extraMetadata["visibilityMode"] = visibilityMode
952
+
953
+ let FLOATEvent <- create FLOATEvent(
954
+ _claimable: claimable,
955
+ _description: description,
956
+ _extraMetadata: extraMetadata,
957
+ _host: self.owner!.address,
958
+ _image: image,
959
+ _name: name,
960
+ _transferrable: transferrable,
961
+ _url: url,
962
+ _verifiers: typedVerifiers
963
+ )
964
+ let eventId = FLOATEvent.eventId
965
+ self.events[eventId] <-! FLOATEvent
966
+
967
+ return eventId
968
+ }
969
+
970
+ // Deletes an event.
971
+ pub fun deleteEvent(eventId: UInt64) {
972
+ let eventRef = self.borrowEventRef(eventId: eventId) ?? panic("This FLOAT does not exist.")
973
+ destroy self.events.remove(key: eventId)
974
+ }
975
+
976
+ pub fun borrowEventRef(eventId: UInt64): &FLOATEvent? {
977
+ return &self.events[eventId] as &FLOATEvent?
978
+ }
979
+
980
+ // Get a public reference to the FLOATEvent
981
+ // so you can call some helpful getters
982
+ pub fun borrowPublicEventRef(eventId: UInt64): &FLOATEvent{FLOATEventPublic}? {
983
+ return &self.events[eventId] as &FLOATEvent{FLOATEventPublic}?
984
+ }
985
+
986
+ pub fun getIDs(): [UInt64] {
987
+ return self.events.keys
988
+ }
989
+
990
+ // Maps the eventId to the name of that
991
+ // event. Just a kind helper.
992
+ pub fun getAllEvents(): {UInt64: String} {
993
+ let answer: {UInt64: String} = {}
994
+ for id in self.events.keys {
995
+ let ref = (&self.events[id] as &FLOATEvent?)!
996
+ answer[id] = ref.name
997
+ }
998
+ return answer
999
+ }
1000
+
1001
+ pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver} {
1002
+ return (&self.events[id] as &{MetadataViews.Resolver}?)!
1003
+ }
1004
+
1005
+ init() {
1006
+ self.events <- {}
1007
+ self.groups <- {}
1008
+ }
1009
+
1010
+ destroy() {
1011
+ destroy self.events
1012
+ destroy self.groups
1013
+ }
1014
+ }
1015
+
1016
+ pub fun createEmptyCollection(): @Collection {
1017
+ return <- create Collection()
1018
+ }
1019
+
1020
+ pub fun createEmptyFLOATEventCollection(): @FLOATEvents {
1021
+ return <- create FLOATEvents()
1022
+ }
1023
+
1024
+ // A function to validate expected FLOAT metadata that must be in a
1025
+ // certain format as to not cause aborts during expected casting
1026
+ pub fun validateExtraFloatMetadata(data: {String: AnyStruct}): Bool {
1027
+ if data.containsKey("medalType") {
1028
+ let medalType: String? = FLOAT.extraMetadataToStrOpt(data, "medalType")
1029
+ if medalType == nil || (medalType != "gold" && medalType != "silver" && medalType != "bronze" && medalType != "participation") {
1030
+ return false
1031
+ }
1032
+ }
1033
+ return true
1034
+ }
1035
+
1036
+ // Helper to cast dictionary value to String? type
1037
+ //
1038
+ // Note about all the casting going on:
1039
+ // You might be saying, "Why are you double force unwrapping
1040
+ // medalType Jacob?" "Why would an unwrapped type still needed to be unwrapped?"
1041
+ // The reason is because in Cadence dictionaries, you can encounter double optionals
1042
+ // where the actual type that lies in the value of a dictionary is itself
1043
+ // nil. In other words, it's possible to have `{ "jacob": nil }` in a dictionary.
1044
+ // So we force unwrap due to the dictionary, then unwrap the value within.
1045
+ // It will never abort because we have checked for nil above, which checks
1046
+ // for both types of nil.
1047
+ pub fun extraMetadataToStrOpt(_ dict: {String: AnyStruct}, _ key: String): String? {
1048
+ // `dict[key] == nil` means:
1049
+ // 1. the key doesn't exist
1050
+ // 2. the value for the key is nil
1051
+ if dict[key] == nil || dict[key]!!.getType() != Type<String>() {
1052
+ return nil
1053
+ }
1054
+ return dict[key]!! as! String
1055
+ }
1056
+
1057
+ /// Function that returns all the Metadata Views implemented by a Non Fungible Token
1058
+ ///
1059
+ /// @return An array of Types defining the implemented views. This value will be used by
1060
+ /// developers to know which parameter to pass to the resolveView() method.
1061
+ ///
1062
+ pub fun getViews(): [Type] {
1063
+ return [
1064
+ Type<MetadataViews.NFTCollectionData>(),
1065
+ Type<MetadataViews.NFTCollectionDisplay>()
1066
+ ]
1067
+ }
1068
+
1069
+ /// Function that resolves a metadata view for this contract.
1070
+ ///
1071
+ /// @param view: The Type of the desired view.
1072
+ /// @return A structure representing the requested view.
1073
+ ///
1074
+ pub fun resolveView(_ view: Type): AnyStruct? {
1075
+ switch view {
1076
+ case Type<MetadataViews.NFTCollectionData>():
1077
+ return MetadataViews.NFTCollectionData(
1078
+ storagePath: FLOAT.FLOATCollectionStoragePath,
1079
+ publicPath: FLOAT.FLOATCollectionPublicPath,
1080
+ providerPath: /private/FLOATCollectionPrivatePath,
1081
+ publicCollection: Type<&Collection{CollectionPublic}>(),
1082
+ publicLinkedType: Type<&Collection{CollectionPublic, NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>(),
1083
+ providerLinkedType: Type<&Collection{CollectionPublic, NonFungibleToken.CollectionPublic, NonFungibleToken.Provider, MetadataViews.ResolverCollection}>(),
1084
+ createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
1085
+ return <- FLOAT.createEmptyCollection()
1086
+ })
1087
+ )
1088
+ case Type<MetadataViews.NFTCollectionDisplay>():
1089
+ let squareMedia: MetadataViews.Media = MetadataViews.Media(
1090
+ file: MetadataViews.HTTPFile(
1091
+ url: "https://i.imgur.com/v0Njnnk.png"
1092
+ ),
1093
+ mediaType: "image"
1094
+ )
1095
+ let bannerMedia: MetadataViews.Media = MetadataViews.Media(
1096
+ file: MetadataViews.HTTPFile(
1097
+ url: "https://i.imgur.com/ETeVZZU.jpg"
1098
+ ),
1099
+ mediaType: "image"
1100
+ )
1101
+ return MetadataViews.NFTCollectionDisplay(
1102
+ name: "FLOAT",
1103
+ description: "FLOAT is a proof of attendance platform on the Flow blockchain.",
1104
+ externalURL: MetadataViews.ExternalURL("https://floats.city"),
1105
+ squareImage: squareMedia,
1106
+ bannerImage: bannerMedia,
1107
+ socials: {
1108
+ "twitter": MetadataViews.ExternalURL("https://twitter.com/emerald_dao"),
1109
+ "discord": MetadataViews.ExternalURL("https://discord.gg/emeraldcity")
1110
+ }
1111
+ )
1112
+ }
1113
+ return nil
1114
+ }
1115
+
1116
+ init() {
1117
+ self.totalSupply = 0
1118
+ self.totalFLOATEvents = 0
1119
+ emit ContractInitialized()
1120
+
1121
+ self.FLOATCollectionStoragePath = /storage/FLOATCollectionStoragePath
1122
+ self.FLOATCollectionPublicPath = /public/FLOATCollectionPublicPath
1123
+ self.FLOATEventsStoragePath = /storage/FLOATEventsStoragePath
1124
+ self.FLOATEventsPrivatePath = /private/FLOATEventsPrivatePath
1125
+ self.FLOATEventsPublicPath = /public/FLOATEventsPublicPath
1126
+
1127
+ // delete later
1128
+
1129
+ if self.account.borrow<&FLOAT.Collection>(from: FLOAT.FLOATCollectionStoragePath) == nil {
1130
+ self.account.save(<- FLOAT.createEmptyCollection(), to: FLOAT.FLOATCollectionStoragePath)
1131
+ self.account.link<&FLOAT.Collection{NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection, FLOAT.CollectionPublic}>
1132
+ (FLOAT.FLOATCollectionPublicPath, target: FLOAT.FLOATCollectionStoragePath)
1133
+ }
1134
+
1135
+ if self.account.borrow<&FLOAT.FLOATEvents>(from: FLOAT.FLOATEventsStoragePath) == nil {
1136
+ self.account.save(<- FLOAT.createEmptyFLOATEventCollection(), to: FLOAT.FLOATEventsStoragePath)
1137
+ self.account.link<&FLOAT.FLOATEvents{FLOAT.FLOATEventsPublic, MetadataViews.ResolverCollection}>
1138
+ (FLOAT.FLOATEventsPublicPath, target: FLOAT.FLOATEventsStoragePath)
1139
+ }
1140
+
1141
+ let FLOATEvents = self.account.borrow<&FLOAT.FLOATEvents>(from: FLOAT.FLOATEventsStoragePath)
1142
+ ?? panic("Could not borrow the FLOATEvents from the signer.")
1143
+
1144
+ var verifiers: [{FLOAT.IVerifier}] = []
1145
+
1146
+ let extraMetadata: {String: AnyStruct} = {}
1147
+
1148
+ extraMetadata["backImage"] = "bafkreihwra72f2sby4h2bswej4zzrmparb6jy55ygjrymxjk572tjziatu"
1149
+ extraMetadata["eventType"] = "course"
1150
+ extraMetadata["certificateImage"] = "bafkreidcwg6jkcsugms2jtv6suwk2cao2ij6y57mopz4p4anpnvwswv2ku"
1151
+
1152
+ FLOATEvents.createEvent(claimable: true, description: "Test description for the upcoming Flow Hackathon. This is soooo fun! Woohoo!", image: "bafybeifpsnwb2vkz4p6nxhgsbwgyslmlfd7jyicx5ukbj3tp7qsz7myzrq", name: "Flow Hackathon", transferrable: true, url: "", verifiers: verifiers, allowMultipleClaim: false, certificateType: "medal", visibilityMode: "certificate", extraMetadata: extraMetadata)
1153
+
1154
+ extraMetadata["backImage"] = "bafkreihwra72f2sby4h2bswej4zzrmparb6jy55ygjrymxjk572tjziatu"
1155
+ extraMetadata["eventType"] = "discordMeeting"
1156
+ extraMetadata["certificateImage"] = "bafkreidcwg6jkcsugms2jtv6suwk2cao2ij6y57mopz4p4anpnvwswv2ku"
1157
+
1158
+ FLOATEvents.createEvent(claimable: true, description: "Test description for a Discord meeting. This is soooo fun! Woohoo!", image: "bafybeifpsnwb2vkz4p6nxhgsbwgyslmlfd7jyicx5ukbj3tp7qsz7myzrq", name: "Discord Meeting", transferrable: true, url: "", verifiers: verifiers, allowMultipleClaim: false, certificateType: "ticket", visibilityMode: "picture", extraMetadata: extraMetadata)
1159
+ }
1160
+ }
1161
+