github.com/DapperCollectives/CAST/backend@v0.0.0-20230921221157-1350c8be7c96/main/cadence/float/FLOAT.cdc (about)

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