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 }