Smart Contract

MusicPeaksMembershipToken

A.a02c28dc0aa50c18.MusicPeaksMembershipToken

Deployed

2d ago
Feb 26, 2026, 11:03:24 AM UTC

Dependents

0 imports
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 MusicPeaksMembershipToken. 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// Lastly, we implemented GrantedAccountAccess.cdc, which allows you to specify
22// someone else can control your account (in the context of FLOAT). This
23// is specifically designed for teams to be able to handle one "host" on the
24// FLOAT platform so all the company's events are under one account.
25// This is mainly used to give other people access to your FLOATEvents resource,
26// and allow them to mint for you and control Admin operations on your events.
27
28// For more info on GrantedAccountAccess, see GrantedAccountAccess.cdc
29
30import NonFungibleToken from 0x1d7e57aa55817448
31import MetadataViews from 0x1d7e57aa55817448
32import FindViews from 0x097bafa4e0b48eef
33import GrantedAccountAccess from 0xa02c28dc0aa50c18
34
35pub contract MusicPeaksMembershipToken: NonFungibleToken {
36
37    /***********************************************/
38    /******************** PATHS ********************/
39    /***********************************************/
40
41    pub let FLOATCollectionStoragePath: StoragePath
42    pub let FLOATCollectionPublicPath: PublicPath
43    pub let FLOATEventsStoragePath: StoragePath
44    pub let FLOATEventsPublicPath: PublicPath
45    pub let FLOATEventsPrivatePath: PrivatePath
46
47    /************************************************/
48    /******************** EVENTS ********************/
49    /************************************************/
50
51    pub event ContractInitialized()
52    pub event FLOATMinted(id: UInt64, eventHost: Address, eventId: UInt64, eventImage: String, recipient: Address, serial: UInt64)
53    pub event FLOATClaimed(id: UInt64, eventHost: Address, eventId: UInt64, eventImage: String, eventName: String, recipient: Address, serial: UInt64)
54    pub event FLOATDestroyed(id: UInt64, eventHost: Address, eventId: UInt64, eventImage: String, serial: UInt64)
55    pub event FLOATTransferred(id: UInt64, eventHost: Address, eventId: UInt64, newOwner: Address?, serial: UInt64)
56    pub event FLOATEventCreated(eventId: UInt64, description: String, host: Address, image: String, name: String, url: String)
57    pub event FLOATEventDestroyed(eventId: UInt64, host: Address, name: String)
58
59    pub event Deposit(id: UInt64, to: Address?)
60    pub event Withdraw(id: UInt64, from: Address?)
61
62    /***********************************************/
63    /******************** STATE ********************/
64    /***********************************************/
65
66    // The total amount of FLOATs that have ever been
67    // created (does not go down when a FLOAT is destroyed)
68    pub var totalSupply: UInt64
69    // The total amount of FLOATEvents that have ever been
70    // created (does not go down when a FLOATEvent is destroyed)
71    pub var totalFLOATEvents: UInt64
72
73    /***********************************************/
74    /**************** FUNCTIONALITY ****************/
75    /***********************************************/
76
77    // A helpful wrapper to contain an address,
78    // the id of a FLOAT, and its serial
79    pub struct TokenIdentifier {
80        pub let id: UInt64
81        pub let address: Address
82        pub let serial: UInt64
83
84        init(_id: UInt64, _address: Address, _serial: UInt64) {
85            self.id = _id
86            self.address = _address
87            self.serial = _serial
88        }
89    }
90
91    pub struct TokenInfo {
92        pub let path: PublicPath
93        pub let price: UFix64
94
95        init(_path: PublicPath, _price: UFix64) {
96            self.path = _path
97            self.price = _price
98        }
99    }
100
101    // Represents a FLOAT
102    pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
103        // The `uuid` of this resource
104        pub let id: UInt64
105
106        // Some of these are also duplicated on the event,
107        // but it's necessary to put them here as well
108        // in case the FLOATEvent host deletes the event
109        pub let dateReceived: UFix64
110        pub let eventDescription: String
111        pub let eventHost: Address
112        pub let eventId: UInt64
113        pub let eventImage: String
114        pub let eventName: String
115        pub let originalRecipient: Address
116        pub let serial: UInt64
117
118        // A capability that points to the FLOATEvents this FLOAT is from.
119        // There is a chance the event host unlinks their event from
120        // the public, in which case it's impossible to know details
121        // about the event. Which is fine, since we store the
122        // crucial data to know about the FLOAT in the FLOAT itself.
123        pub let eventsCap: Capability<&FLOATEvents{FLOATEventsPublic, MetadataViews.ResolverCollection}>
124
125        // Helper function to get the metadata of the event
126        // this MusicPeaksMembershipToken is from.
127        pub fun getEventMetadata(): &FLOATEvent{FLOATEventPublic}? {
128            if let events = self.eventsCap.borrow() {
129                return events.borrowPublicEventRef(eventId: self.eventId)
130            }
131            return nil
132        }
133
134        // This is for the MetdataStandard
135        pub fun getViews(): [Type] {
136            let supportedViews = [
137                Type<MetadataViews.Display>(),
138                Type<MetadataViews.Royalties>(),
139                Type<MetadataViews.ExternalURL>(),
140                Type<MetadataViews.NFTCollectionData>(),
141                Type<MetadataViews.NFTCollectionDisplay>(),
142                Type<MetadataViews.Serial>(),
143                Type<TokenIdentifier>()
144            ]
145
146            if self.getEventMetadata()?.transferrable == false {
147                supportedViews.append(Type<FindViews.SoulBound>())
148            }
149
150            return supportedViews
151        }
152
153        // This is for the MetdataStandard
154        pub fun resolveView(_ view: Type): AnyStruct? {
155            switch view {
156                case Type<MetadataViews.Display>():
157                    return MetadataViews.Display(
158                        name: self.eventName,
159                        description: self.eventDescription,
160                        thumbnail: MetadataViews.HTTPFile(url: "https://musicpeaks.mypinata.cloud/ipfs/".concat(self.eventImage))
161                    )
162                case Type<MetadataViews.Royalties>():
163                    return MetadataViews.Royalties([])
164                case Type<MetadataViews.ExternalURL>():
165                    return MetadataViews.ExternalURL("https://musicpeaks.com/".concat(self.owner!.address.toString()).concat("/float/").concat(self.id.toString()))
166                case Type<MetadataViews.NFTCollectionData>():
167                    return MetadataViews.NFTCollectionData(
168                        storagePath: MusicPeaksMembershipToken.FLOATCollectionStoragePath,
169                        publicPath: MusicPeaksMembershipToken.FLOATCollectionPublicPath,
170                        providerPath: /private/MusicPeaksMembershipTokenCollectionPrivatePath,
171                        publicCollection: Type<&Collection{CollectionPublic}>(),
172                        publicLinkedType: Type<&Collection{CollectionPublic, NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>(),
173                        providerLinkedType: Type<&Collection{CollectionPublic, NonFungibleToken.CollectionPublic, NonFungibleToken.Provider, MetadataViews.ResolverCollection}>(),
174                        createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
175                            return <- MusicPeaksMembershipToken.createEmptyCollection()
176                        })
177                    )
178                case Type<MetadataViews.NFTCollectionDisplay>():
179                    let squareMedia = MetadataViews.Media(
180                        file: MetadataViews.HTTPFile(
181                           url: "https://rockpeaksassets.s3.amazonaws.com/musicpeaks/MP-Square-Logo-Purple-White.png"
182                        ),
183                        mediaType: "image"
184                    )
185                    let bannerMedia = MetadataViews.Media(
186                        file: MetadataViews.HTTPFile(
187                            url: "https://rockpeaksassets.s3.amazonaws.com/musicpeaks/MP-Banner-Logo.png"
188                        ),
189                        mediaType: "image"
190                    )
191                    return MetadataViews.NFTCollectionDisplay(
192                        name: "MusicPeaksMembershipToken",
193                        description: "MusicPeaksMembershipToken is a proof of attendance platform on the Flow blockchain.",
194                        externalURL: MetadataViews.ExternalURL("https://musicpeaks.com/membership/".concat(self.eventHost.toString()).concat("/").concat(self.eventId.toString())),
195                        squareImage: squareMedia,
196                        bannerImage: bannerMedia,
197                        socials: {
198                            "twitter": MetadataViews.ExternalURL("https://twitter.com/MusicPeaks")
199                        }
200                    )
201                case Type<MetadataViews.Serial>():
202                    return MetadataViews.Serial(
203                        self.serial
204                    )
205                case Type<TokenIdentifier>():
206                    return TokenIdentifier(
207                        _id: self.id,
208                        _address: self.owner!.address,
209                        _serial: self.serial
210                    )
211                case Type<FindViews.SoulBound>():
212                    if self.getEventMetadata()?.transferrable == false {
213                        return FindViews.SoulBound(
214                            "This FLOAT is soulbound because the event host toggled off transferring."
215                        )
216                    }
217                    return nil
218            }
219
220            return nil
221        }
222
223        init(_eventDescription: String, _eventHost: Address, _eventId: UInt64, _eventImage: String, _eventName: String, _originalRecipient: Address, _serial: UInt64) {
224            self.id = self.uuid
225            self.dateReceived = getCurrentBlock().timestamp
226            self.eventDescription = _eventDescription
227            self.eventHost = _eventHost
228            self.eventId = _eventId
229            self.eventImage = _eventImage
230            self.eventName = _eventName
231            self.originalRecipient = _originalRecipient
232            self.serial = _serial
233
234            // Stores a capability to the FLOATEvents of its creator
235            self.eventsCap = getAccount(_eventHost)
236                .getCapability<&FLOATEvents{FLOATEventsPublic, MetadataViews.ResolverCollection}>(MusicPeaksMembershipToken.FLOATEventsPublicPath)
237
238            emit FLOATMinted(
239                id: self.id,
240                eventHost: _eventHost,
241                eventId: _eventId,
242                eventImage: _eventImage,
243                recipient: _originalRecipient,
244                serial: _serial
245            )
246
247            MusicPeaksMembershipToken.totalSupply = MusicPeaksMembershipToken.totalSupply + 1
248        }
249
250        destroy() {
251            // If the FLOATEvent owner decided to unlink their public reference
252            // for some reason (heavily recommend against it), their records
253            // of who owns the MusicPeaksMembershipToken is going to be messed up. But that is their
254            // fault. We shouldn't let that prevent the user from deleting the MusicPeaksMembershipToken.
255            if let floatEvent: &FLOATEvent{FLOATEventPublic} = self.getEventMetadata() {
256                floatEvent.updateFLOATHome(id: self.id, serial: self.serial, owner: nil)
257            }
258            emit FLOATDestroyed(
259                id: self.id,
260                eventHost: self.eventHost,
261                eventId: self.eventId,
262                eventImage: self.eventImage,
263                serial: self.serial
264            )
265        }
266    }
267
268    // A public interface for people to call into our Collection
269    pub resource interface CollectionPublic {
270        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
271        pub fun borrowFLOAT(id: UInt64): &NFT?
272        pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver}
273        pub fun deposit(token: @NonFungibleToken.NFT)
274        pub fun getIDs(): [UInt64]
275        pub fun getAllIDs(): [UInt64]
276        pub fun ownedIdsFromEvent(eventId: UInt64): [UInt64]
277    }
278
279    // A Collection that holds all of the users FLOATs.
280    // Withdrawing is not allowed. You can only transfer.
281    pub resource Collection: NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection, CollectionPublic {
282        // Maps a FLOAT id to the FLOAT itself
283        pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
284        // Maps an eventId to the ids of FLOATs that
285        // this user owns from that event. It's possible
286        // for it to be out of sync until June 2022 spork,
287        // but it is used merely as a helper, so that's okay.
288        access(account) var events: {UInt64: {UInt64: Bool}}
289
290        // Deposits a FLOAT to the collection
291        pub fun deposit(token: @NonFungibleToken.NFT) {
292            let nft <- token as! @NFT
293            let id = nft.id
294            let eventId = nft.eventId
295
296            // Update self.events[eventId] to have
297            // this FLOAT's id in it
298            if self.events[eventId] == nil {
299                self.events[eventId] = {id: true}
300            } else {
301                self.events[eventId]!.insert(key: id, true)
302            }
303
304            // Try to update the FLOATEvent's current holders. This will
305            // not work if they unlinked their FLOATEvent to the public,
306            // and the data will go out of sync. But that is their fault.
307            if let floatEvent: &FLOATEvent{FLOATEventPublic} = nft.getEventMetadata() {
308                floatEvent.updateFLOATHome(id: id, serial: nft.serial, owner: self.owner!.address)
309            }
310
311            emit Deposit(id: id, to: self.owner!.address)
312            self.ownedNFTs[id] <-! nft
313        }
314
315        pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
316            let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("You do not own this FLOAT in your collection")
317            let nft <- token as! @NFT
318
319            // Update self.events[eventId] to not
320            // have this FLOAT's id in it
321            self.events[nft.eventId]!.remove(key: withdrawID)
322
323            // Try to update the FLOATEvent's current holders. This will
324            // not work if they unlinked their FLOATEvent to the public,
325            // and the data will go out of sync. But that is their fault.
326            //
327            // Additionally, this checks if the FLOATEvent host wanted this
328            // FLOAT to be transferrable. Secondary marketplaces will use this
329            // withdraw function, so if the FLOAT is not transferrable,
330            // you can't sell it there.
331            if let floatEvent: &FLOATEvent{FLOATEventPublic} = nft.getEventMetadata() {
332                assert(
333                    floatEvent.transferrable,
334                    message: "This FLOAT is not transferrable."
335                )
336                floatEvent.updateFLOATHome(id: withdrawID, serial: nft.serial, owner: nil)
337            }
338
339            emit Withdraw(id: withdrawID, from: self.owner!.address)
340            return <- nft
341        }
342
343        pub fun delete(id: UInt64) {
344            let token <- self.ownedNFTs.remove(key: id) ?? panic("You do not own this FLOAT in your collection")
345            let nft <- token as! @NFT
346
347            // Update self.events[eventId] to not
348            // have this FLOAT's id in it
349            self.events[nft.eventId]!.remove(key: id)
350
351            destroy nft
352        }
353
354        // Only returns the FLOATs for which we can still
355        // access data about their event.
356        pub fun getIDs(): [UInt64] {
357            let ids: [UInt64] = []
358            for key in self.ownedNFTs.keys {
359                let nftRef = self.borrowFLOAT(id: key)!
360                if nftRef.eventsCap.check() {
361                    ids.append(key)
362                }
363            }
364            return ids
365        }
366
367        // Returns all the FLOATs ids
368        pub fun getAllIDs(): [UInt64] {
369            return self.ownedNFTs.keys
370        }
371
372        // Returns an array of ids that belong to
373        // the passed in eventId
374        //
375        // It's possible for FLOAT ids to be present that
376        // shouldn't be if people tried to withdraw directly
377        // from `ownedNFTs` (not possible after June 2022 spork),
378        // but this makes sure the returned
379        // ids are all actually owned by this account.
380        pub fun ownedIdsFromEvent(eventId: UInt64): [UInt64] {
381            let answer: [UInt64] = []
382            if let idsInEvent = self.events[eventId]?.keys {
383                for id in idsInEvent {
384                    if self.ownedNFTs[id] != nil {
385                        answer.append(id)
386                    }
387                }
388            }
389            return answer
390        }
391
392        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
393            return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
394        }
395
396        pub fun borrowFLOAT(id: UInt64): &NFT? {
397            if self.ownedNFTs[id] != nil {
398                let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
399                return ref as! &NFT
400            }
401            return nil
402        }
403
404        pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver} {
405            let tokenRef = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
406            let nftRef = tokenRef as! &NFT
407            return nftRef as &{MetadataViews.Resolver}
408        }
409
410        init() {
411            self.ownedNFTs <- {}
412            self.events = {}
413        }
414
415        destroy() {
416            destroy self.ownedNFTs
417        }
418    }
419
420    // An interface that every "verifier" must implement.
421    // A verifier is one of the options on the FLOAT Event page,
422    // for example, a "time limit," or a "limited" number of
423    // FLOATs that can be claimed.
424    // All the current verifiers can be seen inside FLOATVerifiers.cdc
425    pub struct interface IVerifier {
426        // A function every verifier must implement.
427        // Will have `assert`s in it to make sure
428        // the user fits some criteria.
429        access(account) fun verify(_ params: {String: AnyStruct})
430    }
431
432    // A public interface to read the FLOATEvent
433    pub resource interface FLOATEventPublic {
434        pub var claimable: Bool
435        pub let dateCreated: UFix64
436        pub let description: String
437        pub let eventId: UInt64
438        pub let host: Address
439        pub let image: String
440        pub let name: String
441        pub var totalSupply: UInt64
442        pub var transferrable: Bool
443        pub let url: String
444        pub fun claim(recipient: &Collection, params: {String: AnyStruct})
445        pub fun getClaimed(): {Address: TokenIdentifier}
446        pub fun getCurrentHolders(): {UInt64: TokenIdentifier}
447        pub fun getCurrentHolder(serial: UInt64): TokenIdentifier?
448        pub fun getExtraMetadata(): {String: AnyStruct}
449        pub fun getVerifiers(): {String: [{IVerifier}]}
450        pub fun getGroups(): [String]
451        pub fun getPrices(): {String: TokenInfo}?
452        pub fun hasClaimed(account: Address): TokenIdentifier?
453
454        access(account) fun updateFLOATHome(id: UInt64, serial: UInt64, owner: Address?)
455    }
456
457    //
458    // FLOATEvent
459    //
460    pub resource FLOATEvent: FLOATEventPublic, MetadataViews.Resolver {
461        // Whether or not users can claim from our event (can be toggled
462        // at any time)
463        pub var claimable: Bool
464        // Maps an address to the FLOAT they claimed
465        access(account) var claimed: {Address: TokenIdentifier}
466        // Maps a serial to the person who theoretically owns
467        // that MusicPeaksMembershipToken. Must be serial --> TokenIdentifier because
468        // it's possible someone has multiple FLOATs from this event.
469        access(account) var currentHolders: {UInt64: TokenIdentifier}
470        pub let dateCreated: UFix64
471        pub let description: String
472        // This is equal to this resource's uuid
473        pub let eventId: UInt64
474        access(account) var extraMetadata: {String: AnyStruct}
475        // The groups that this FLOAT Event belongs to (groups
476        // are within the FLOATEvents resource)
477        access(account) var groups: {String: Bool}
478        // Who created this FLOAT Event
479        pub let host: Address
480        // The image of the FLOAT Event
481        pub let image: String
482        // The name of the FLOAT Event
483        pub let name: String
484        // The total number of FLOATs that have been
485        // minted from this event
486        pub var totalSupply: UInt64
487        // Whether or not the FLOATs that users own
488        // from this event can be transferred on the
489        // FLOAT platform itself (transferring allowed
490        // elsewhere)
491        pub var transferrable: Bool
492        // A url of where the event took place
493        pub let url: String
494        // A list of verifiers this FLOAT Event contains.
495        // Will be used every time someone "claims" a FLOAT
496        // to see if they pass the requirements
497        access(account) let verifiers: {String: [{IVerifier}]}
498
499        /***************** Setters for the Event Owner *****************/
500
501        // Toggles claiming on/off
502        pub fun toggleClaimable(): Bool {
503            self.claimable = !self.claimable
504            return self.claimable
505        }
506
507        // Toggles transferring on/off
508        pub fun toggleTransferrable(): Bool {
509            self.transferrable = !self.transferrable
510            return self.transferrable
511        }
512
513        // Updates the metadata in case you want
514        // to add something.
515        pub fun updateMetadata(newExtraMetadata: {String: AnyStruct}) {
516            for key in newExtraMetadata.keys {
517                self.extraMetadata[key] = newExtraMetadata[key]
518            }
519        }
520
521        /***************** Setters for the Contract Only *****************/
522
523        // Called if a user moves their FLOAT to another location.
524        // Needed so we can keep track of who currently has it.
525        access(account) fun updateFLOATHome(id: UInt64, serial: UInt64, owner: Address?) {
526            if owner == nil {
527                self.currentHolders.remove(key: serial)
528            } else {
529                self.currentHolders[serial] = TokenIdentifier(
530                    _id: id,
531                    _address: owner!,
532                    _serial: serial
533                )
534            }
535            emit FLOATTransferred(id: id, eventHost: self.host, eventId: self.eventId, newOwner: owner, serial: serial)
536        }
537
538        // Adds this FLOAT Event to a group
539        access(account) fun addToGroup(groupName: String) {
540            self.groups[groupName] = true
541        }
542
543        // Removes this FLOAT Event to a group
544        access(account) fun removeFromGroup(groupName: String) {
545            self.groups.remove(key: groupName)
546        }
547
548        /***************** Getters (all exposed to the public) *****************/
549
550        // Returns info about the FLOAT that this account claimed
551        // (if any)
552        pub fun hasClaimed(account: Address): TokenIdentifier? {
553            return self.claimed[account]
554        }
555
556        // This is a guarantee that the person owns the FLOAT
557        // with the passed in serial
558        pub fun getCurrentHolder(serial: UInt64): TokenIdentifier? {
559            pre {
560                self.currentHolders[serial] != nil:
561                    "This serial has not been created yet."
562            }
563            let data = self.currentHolders[serial]!
564            let collection = getAccount(data.address).getCapability(MusicPeaksMembershipToken.FLOATCollectionPublicPath).borrow<&Collection{CollectionPublic}>()
565            if collection?.borrowFLOAT(id: data.id) != nil {
566                return data
567            }
568
569            return nil
570        }
571
572        // Returns an accurate dictionary of all the
573        // claimers
574        pub fun getClaimed(): {Address: TokenIdentifier} {
575            return self.claimed
576        }
577
578        // This dictionary may be slightly off if for some
579        // reason the FLOATEvents owner ever unlinked their
580        // resource from the public.
581        // Use `getCurrentHolder(serial: UInt64)` to truly
582        // verify if someone holds that serial.
583        pub fun getCurrentHolders(): {UInt64: TokenIdentifier} {
584            return self.currentHolders
585        }
586
587        pub fun getExtraMetadata(): {String: AnyStruct} {
588            return self.extraMetadata
589        }
590
591        // Gets all the verifiers that will be used
592        // for claiming
593        pub fun getVerifiers(): {String: [{IVerifier}]} {
594            return self.verifiers
595        }
596
597        pub fun getGroups(): [String] {
598            return self.groups.keys
599        }
600
601        pub fun getViews(): [Type] {
602             return [
603                Type<MetadataViews.Display>()
604            ]
605        }
606
607        pub fun getPrices(): {String: TokenInfo}? {
608            if let prices = self.extraMetadata["prices"] {
609                return prices as! {String: TokenInfo}
610            }
611            return nil
612        }
613
614        pub fun resolveView(_ view: Type): AnyStruct? {
615            switch view {
616                case Type<MetadataViews.Display>():
617                    return MetadataViews.Display(
618                        name: self.name,
619                        description: self.description,
620                        thumbnail: MetadataViews.IPFSFile(cid: self.image, path: nil)
621                    )
622            }
623
624            return nil
625        }
626
627        /****************** Getting a FLOAT ******************/
628
629        // Will not panic if one of the recipients has already claimed.
630        // It will just skip them.
631        pub fun batchMint(recipients: [&Collection{NonFungibleToken.CollectionPublic}]) {
632            for recipient in recipients {
633                if self.claimed[recipient.owner!.address] == nil {
634                    self.mint(recipient: recipient, params: {})
635                }
636            }
637        }
638
639        // Used to give a person a FLOAT from this event.
640        // Used as a helper function for `claim`, but can also be
641        // used by the event owner and shared accounts to
642        // mint directly to a user.
643        //
644        // If the event owner directly mints to a user, it does not
645        // run the verifiers on the user. It bypasses all of them.
646        //
647        // Return the id of the FLOAT it minted
648        pub fun mint(recipient: &Collection{NonFungibleToken.CollectionPublic}, params: {String: AnyStruct}): UInt64 {
649            pre {
650                self.claimed[recipient.owner!.address] == nil:
651                    "This person already claimed their FLOAT!"
652            }
653            let recipientAddr: Address = recipient.owner!.address
654            let serial = self.totalSupply
655
656            let token <- create NFT(
657                _eventDescription: self.description,
658                _eventHost: self.host,
659                _eventId: self.eventId,
660                _eventImage: self.image,
661                _eventName: self.name,
662                _originalRecipient: recipientAddr,
663                _serial: serial
664            )
665            let id = token.id
666            // Saves the claimer
667            self.claimed[recipientAddr] = TokenIdentifier(
668                _id: id,
669                _address: recipientAddr,
670                _serial: serial
671            )
672            // Saves the claimer as the current holder
673            // of the newly minted FLOAT
674            self.currentHolders[serial] = TokenIdentifier(
675                _id: id,
676                _address: recipientAddr,
677                _serial: serial
678            )
679
680            let metadata: {String: AnyStruct} = {}
681            let currentBlock = getCurrentBlock()
682            metadata["mintedTime"] = currentBlock.timestamp
683            if params.containsKey("ipfsUrl") {
684                let ipfsUrl = params!["ipfsUrl"]! as! String
685                metadata["ipfsUrl"] = ipfsUrl
686            }
687            self.extraMetadata[id.toString()] = metadata
688
689            self.totalSupply = self.totalSupply + 1
690            recipient.deposit(token: <- token)
691            return id
692        }
693
694        access(account) fun verifyAndMint(recipient: &Collection, params: {String: AnyStruct}): UInt64 {
695            params["event"] = &self as &FLOATEvent{FLOATEventPublic}
696            params["claimee"] = recipient.owner!.address
697
698            // Runs a loop over all the verifiers that this FLOAT Events
699            // implements. For example, "Limited", "Timelock", "Secret", etc.
700            // All the verifiers are in the FLOATVerifiers.cdc contract
701            for identifier in self.verifiers.keys {
702                let typedModules = (&self.verifiers[identifier] as &[{IVerifier}]?)!
703                var i = 0
704                while i < typedModules.length {
705                    let verifier = &typedModules[i] as &{IVerifier}
706                    verifier.verify(params)
707                    i = i + 1
708                }
709            }
710
711            // You're good to go.
712            let id = self.mint(recipient: recipient, params: params)
713
714            emit FLOATClaimed(
715                id: id,
716                eventHost: self.host,
717                eventId: self.eventId,
718                eventImage: self.image,
719                eventName: self.name,
720                recipient: recipient.owner!.address,
721                serial: self.totalSupply - 1
722            )
723            return id
724        }
725
726        // For the public to claim FLOATs. Must be claimable to do so.
727        // You can pass in `params` that will be forwarded to the
728        // customized `verify` function of the verifier.
729        //
730        // For example, the FLOAT platform allows event hosts
731        // to specify a secret phrase. That secret phrase will
732        // be passed in the `params`.
733        pub fun claim(recipient: &Collection, params: {String: AnyStruct}) {
734            pre {
735                self.getPrices() == nil:
736                    "You need to purchase this MusicPeaksMembershipToken."
737                self.claimed[recipient.owner!.address] == nil:
738                    "This person already claimed their FLOAT!"
739                self.claimable:
740                    "This FLOATEvent is not claimable, and thus not currently active."
741            }
742
743            self.verifyAndMint(recipient: recipient, params: params)
744        }
745
746        init (
747            _claimable: Bool,
748            _description: String,
749            _extraMetadata: {String: AnyStruct},
750            _host: Address,
751            _image: String,
752            _name: String,
753            _transferrable: Bool,
754            _url: String,
755            _verifiers: {String: [{IVerifier}]},
756        ) {
757            self.claimable = _claimable
758            self.claimed = {}
759            self.currentHolders = {}
760            self.dateCreated = getCurrentBlock().timestamp
761            self.description = _description
762            self.eventId = self.uuid
763            self.extraMetadata = _extraMetadata
764            self.groups = {}
765            self.host = _host
766            self.image = _image
767            self.name = _name
768            self.transferrable = _transferrable
769            self.totalSupply = 0
770            self.url = _url
771            self.verifiers = _verifiers
772
773            MusicPeaksMembershipToken.totalFLOATEvents = MusicPeaksMembershipToken.totalFLOATEvents + 1
774            emit FLOATEventCreated(eventId: self.eventId, description: self.description, host: self.host, image: self.image, name: self.name, url: self.url)
775        }
776
777        destroy() {
778            emit FLOATEventDestroyed(eventId: self.eventId, host: self.host, name: self.name)
779        }
780    }
781
782    // A container of FLOAT Events (maybe because they're similar to
783    // one another, or an event host wants to list all their AMAs together, etc).
784    pub resource Group {
785        pub let id: UInt64
786        pub let name: String
787        pub let image: String
788        pub let description: String
789        // All the FLOAT Events that belong
790        // to this group.
791        access(account) var events: {UInt64: Bool}
792
793        access(account) fun addEvent(eventId: UInt64) {
794            self.events[eventId] = true
795        }
796
797        access(account) fun removeEvent(eventId: UInt64) {
798            self.events.remove(key: eventId)
799        }
800
801        pub fun getEvents(): [UInt64] {
802            return self.events.keys
803        }
804
805        init(_name: String, _image: String, _description: String) {
806            self.id = self.uuid
807            self.name = _name
808            self.image = _image
809            self.description = _description
810            self.events = {}
811        }
812    }
813
814    //
815    // FLOATEvents
816    //
817    pub resource interface FLOATEventsPublic {
818        // Public Getters
819        pub fun borrowPublicEventRef(eventId: UInt64): &FLOATEvent{FLOATEventPublic}?
820        pub fun getAllEvents(): {UInt64: String}
821        pub fun getIDs(): [UInt64]
822        pub fun getGroup(groupName: String): &Group?
823        pub fun getGroups(): [String]
824        // Account Getters
825        access(account) fun borrowEventsRef(): &FLOATEvents
826    }
827
828    // A "Collection" of FLOAT Events
829    pub resource FLOATEvents: FLOATEventsPublic, MetadataViews.ResolverCollection {
830        // All the FLOAT Events this collection stores
831        access(account) var events: @{UInt64: FLOATEvent}
832        // All the Groups this collection stores
833        access(account) var groups: @{String: Group}
834
835        // Creates a new FLOAT Event by passing in some basic parameters
836        // and a list of all the verifiers this event must abide by
837        pub fun createEvent(
838            claimable: Bool,
839            description: String,
840            image: String,
841            name: String,
842            transferrable: Bool,
843            url: String,
844            verifiers: [{IVerifier}],
845            _ extraMetadata: {String: AnyStruct},
846            initialGroups: [String]
847        ): UInt64 {
848            let typedVerifiers: {String: [{IVerifier}]} = {}
849            for verifier in verifiers {
850                let identifier = verifier.getType().identifier
851                if typedVerifiers[identifier] == nil {
852                    typedVerifiers[identifier] = [verifier]
853                } else {
854                    typedVerifiers[identifier]!.append(verifier)
855                }
856            }
857
858            let FLOATEvent <- create FLOATEvent(
859                _claimable: claimable,
860                _description: description,
861                _extraMetadata: extraMetadata,
862                _host: self.owner!.address,
863                _image: image,
864                _name: name,
865                _transferrable: transferrable,
866                _url: url,
867                _verifiers: typedVerifiers
868            )
869            let eventId = FLOATEvent.eventId
870            self.events[eventId] <-! FLOATEvent
871
872            for groupName in initialGroups {
873                self.addEventToGroup(groupName: groupName, eventId: eventId)
874            }
875            return eventId
876        }
877
878        // Deletes an event. Also makes sure to remove
879        // the event from all the groups its in.
880        pub fun deleteEvent(eventId: UInt64) {
881            let eventRef = self.borrowEventRef(eventId: eventId) ?? panic("This FLOAT does not exist.")
882            for groupName in eventRef.getGroups() {
883                let groupRef = (&self.groups[groupName] as &Group?)!
884                groupRef.removeEvent(eventId: eventId)
885            }
886            destroy self.events.remove(key: eventId)
887        }
888
889        pub fun createGroup(groupName: String, image: String, description: String) {
890            pre {
891                self.groups[groupName] == nil: "A group with this name already exists."
892            }
893            self.groups[groupName] <-! create Group(_name: groupName, _image: image, _description: description)
894        }
895
896        // Deletes a group. Also makes sure to remove
897        // the group from all the events that use it.
898        pub fun deleteGroup(groupName: String) {
899            let eventsInGroup = self.groups[groupName]?.getEvents()
900                                ?? panic("This Group does not exist.")
901            for eventId in eventsInGroup {
902                let ref = (&self.events[eventId] as &FLOATEvent?)!
903                ref.removeFromGroup(groupName: groupName)
904            }
905            destroy self.groups.remove(key: groupName)
906        }
907
908        // Adds an event to a group. Also adds the group
909        // to the event.
910        pub fun addEventToGroup(groupName: String, eventId: UInt64) {
911            pre {
912                self.groups[groupName] != nil: "This group does not exist."
913                self.events[eventId] != nil: "This event does not exist."
914            }
915            let groupRef = (&self.groups[groupName] as &Group?)!
916            groupRef.addEvent(eventId: eventId)
917
918            let eventRef = self.borrowEventRef(eventId: eventId)!
919            eventRef.addToGroup(groupName: groupName)
920        }
921
922        // Simply takes the event away from the group
923        pub fun removeEventFromGroup(groupName: String, eventId: UInt64) {
924            pre {
925                self.groups[groupName] != nil: "This group does not exist."
926                self.events[eventId] != nil: "This event does not exist."
927            }
928            let groupRef = (&self.groups[groupName] as &Group?)!
929            groupRef.removeEvent(eventId: eventId)
930
931            let eventRef = self.borrowEventRef(eventId: eventId)!
932            eventRef.removeFromGroup(groupName: groupName)
933        }
934
935        pub fun getGroup(groupName: String): &Group? {
936            return &self.groups[groupName] as &Group?
937        }
938
939        pub fun getGroups(): [String] {
940            return self.groups.keys
941        }
942
943        // Only accessible to people who share your account.
944        // If `fromHost` has allowed you to share your account
945        // in the GrantedAccountAccess.cdc contract, you can get a reference
946        // to their FLOATEvents here and do pretty much whatever you want.
947        pub fun borrowSharedRef(fromHost: Address): &FLOATEvents {
948            let sharedInfo = getAccount(fromHost).getCapability(GrantedAccountAccess.InfoPublicPath)
949                                .borrow<&GrantedAccountAccess.Info{GrantedAccountAccess.InfoPublic}>()
950                                ?? panic("Cannot borrow the InfoPublic from the host")
951            assert(
952                sharedInfo.isAllowed(account: self.owner!.address),
953                message: "This account owner does not share their account with you."
954            )
955            let otherFLOATEvents = getAccount(fromHost).getCapability(MusicPeaksMembershipToken.FLOATEventsPublicPath)
956                                    .borrow<&FLOATEvents{FLOATEventsPublic}>()
957                                    ?? panic("Could not borrow the public FLOATEvents.")
958            return otherFLOATEvents.borrowEventsRef()
959        }
960
961        // Only used for the above function.
962        access(account) fun borrowEventsRef(): &FLOATEvents {
963            return &self as &FLOATEvents
964        }
965
966        pub fun borrowEventRef(eventId: UInt64): &FLOATEvent? {
967            return &self.events[eventId] as &FLOATEvent?
968        }
969
970        /************* Getters (for anyone) *************/
971
972        // Get a public reference to the FLOATEvent
973        // so you can call some helpful getters
974        pub fun borrowPublicEventRef(eventId: UInt64): &FLOATEvent{FLOATEventPublic}? {
975            return &self.events[eventId] as &FLOATEvent{FLOATEventPublic}?
976        }
977
978        pub fun getIDs(): [UInt64] {
979            return self.events.keys
980        }
981
982        // Maps the eventId to the name of that
983        // event. Just a kind helper.
984        pub fun getAllEvents(): {UInt64: String} {
985            let answer: {UInt64: String} = {}
986            for id in self.events.keys {
987                let ref = (&self.events[id] as &FLOATEvent?)!
988                answer[id] = ref.name
989            }
990            return answer
991        }
992
993        pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver} {
994            return (&self.events[id] as &{MetadataViews.Resolver}?)!
995        }
996
997        init() {
998            self.events <- {}
999            self.groups <- {}
1000        }
1001
1002        destroy() {
1003            destroy self.events
1004            destroy self.groups
1005        }
1006    }
1007
1008    pub fun createEmptyCollection(): @Collection {
1009        return <- create Collection()
1010    }
1011
1012    pub fun createEmptyFLOATEventCollection(): @FLOATEvents {
1013        return <- create FLOATEvents()
1014    }
1015
1016    init() {
1017        self.totalSupply = 0
1018        self.totalFLOATEvents = 0
1019        emit ContractInitialized()
1020
1021        self.FLOATCollectionStoragePath = /storage/MusicPeaksMembershipTokenCollectionStoragePath
1022        self.FLOATCollectionPublicPath = /public/MusicPeaksMembershipTokenCollectionPublicPath
1023        self.FLOATEventsStoragePath = /storage/MusicPeaksMembershipTokenEventsStoragePath
1024        self.FLOATEventsPrivatePath = /private/MusicPeaksMembershipTokenEventsPrivatePath
1025        self.FLOATEventsPublicPath = /public/MusicPeaksMembershipTokenEventsPublicPath
1026    }
1027}
1028