Smart Contract

FlowtyRentals

A.5c57f79c6694797f.FlowtyRentals

Deployed

1d ago
Feb 25, 2026, 11:48:26 PM UTC

Dependents

8 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3import MetadataViews from 0x1d7e57aa55817448
4import LostAndFound from 0x473d6a2c37eab5be
5import RoyaltiesLedger from 0x5c57f79c6694797f
6import FlowtyUtils from 0x3cdbb3d569211ff3
7import Flowty from 0x5c57f79c6694797f
8import FlowtyListingCallback from 0x3cdbb3d569211ff3
9import DNAHandler from 0x3cdbb3d569211ff3
10import Burner from 0xf233dcee88fe0abe
11
12// FlowtyRentals
13//
14// A smart contract responsible for the letting accounts take temporary ownership
15// of assets. It allows owners to list items they own for a fee + deposit and a term
16// that other accounts can then rent. If the item being rented is returned before
17// the term is up, their deposit is returned. If, however, they do not return the rented
18// item by this time, they might lose their deposit if settlement cannot facilitate
19// automatically returning the asset.
20//
21// Each account that wants to list a an item for rent installs a Storefront.
22// Storefronts can list items for rent. There is one Storefront per account,
23// it handles rentals of all NFT types for that account.
24//
25// Each NFT may be listed in one or more Listings, the validity of each
26// Listing can easily be checked.
27//
28// Lenders can watch for Listing events and check the NFT type and
29// ID to see if they wish to rent the listing on a storefront.
30access(all) contract FlowtyRentals {
31    access(all) entitlement List
32    access(all) entitlement Cancel
33    access(all) entitlement Administrator
34
35
36    // FlowtyRentalsInitialized
37    // This contract has been deployed
38    access(all) event FlowtyRentalsInitialized()
39
40    // FlowtyRentalsStorefrontInitialized
41    // A FlowtyRentalsStorefront resource has been created.
42    // Event consumers can now expect events from this FlowtyRentalsStorefront.
43    // Note that we do not specify an address: we cannot and should not.
44    // Created resources do not have an owner address, and may be moved
45    // after creation in ways we cannot check.
46    // ListingAvailable events can be used to determine the address
47    // of the owner of the FlowtyRentalsStorefront (...its location) at the time of
48    // the listing but only at that precise moment in that precise transaction.
49    // If the seller moves the FlowtyRentalsStorefront while the listing is valid,
50    // that is on them.
51    //
52    access(all) event FlowtyRentalsStorefrontInitialized(flowtyRentalsStorefrontResourceID: UInt64)
53
54    // FlowtyRentalsStorefrontfrontDestroyed
55    // A FlowtyRentalsStorefront has been destroyed.
56    // Event consumers can now stop processing events from this FlowtyRentalsStorefront.
57    // Note that we do not specify an address.
58    //
59    access(all) event FlowtyRentalsStorefrontDestroyed(flowtyRentalsStorefrontResourceID: UInt64)
60
61    // FlowtyRentalsMarketplaceInitialized
62    // A FlowtyRentalsMarketplace resource has been created.
63    // Event consumers can now expect events from this FlowtyRentalsMarketplace.
64    // Note that we do not specify an address: we cannot and should not.
65    // Created resources do not have an owner address, and may be moved
66    // after creation in ways we cannot check.
67    // While additional FlowtyRentalsMarketplace resources can be made, this contract will,
68    // by default, access the one stored in this contract address's account. Any listing that is
69    // funded will get routed to and stored there.
70    //
71    access(all) event FlowtyRentalsMarketplaceInitialized(flowtyRentalsMarketplaceResourceID: UInt64)
72
73    // FlowtyRentalsMarketplaceDestroyed
74    // A FlowtyRentalsMarketplace has been destroyed.
75    // Event consumers can now stop processing events from this FlowtyRentalsMarketplace.
76    // Note that we do not specify an address.
77    //
78    access(all) event FlowtyRentalsMarketplaceDestroyed(flowtyRentalsMarketplaceResourceID: UInt64)
79
80    // ListingAvailable
81    // A listing has been created and added to a FlowtyRentalsStorefront resource.
82    // The address values here are valid when the event is emitted, but
83    // the state of the accounts they refer to may be changed outside of the
84    // FlowtyRentalsStorefront workflow, so be careful to check before using them.
85    //
86    access(all) event ListingAvailable(
87        flowtyStorefrontAddress: Address,
88        flowtyStorefrontID: UInt64,
89        listingResourceID: UInt64,
90        nftType: String,
91        nftID: UInt64,
92        amount: UFix64,
93        deposit: UFix64,
94        term: UFix64,
95        royaltyRate: UFix64,
96        expiresAfter: UFix64,
97        paymentTokenType: String,
98        renter: Address?
99    )
100
101    // ListingRented
102    // A Rental has been created and added to a FlowtyRentalsStorefront resource
103    // The address values here are valid when an event is emitted, but the state
104    // of the accounts they refer to may be changed outside of the
105    // FlowtyRentalsMarketplace workflow, so be careful to check when using them.
106    //
107    access(all) event ListingRented(
108        flowtyStorefrontAddress: Address,
109        flowtyStorefrontID: UInt64,
110        renterAddress: Address,
111        listingResourceID: UInt64,
112        rentalResourceID: UInt64,
113        nftID: UInt64,
114        nftType: String,
115        amount: UFix64,
116        deposit: UFix64,
117        enabledAutomaticReturn: Bool
118    )
119
120    // RentalReturned
121    // A rental has been returned, releasing the deposit back to its renter
122    //
123    access(all) event RentalReturned(
124        flowtyStorefrontAddress: Address,
125        flowtyStorefrontID: UInt64,
126        renterAddress: Address,
127        listingResourceID: UInt64,
128        rentalResourceID: UInt64,
129        nftID: UInt64,
130        nftType: String
131    )
132
133    // ListingDestroyed
134    // Listing has been destroyed and has been removed from
135    // the owning account's FlowtyRentalsStorefront
136    //
137    access(all) event ListingDestroyed(
138        flowtyStorefrontAddress: Address,
139        flowtyStorefrontID: UInt64,
140        listingResourceID: UInt64,
141        nftID: UInt64,
142        nftType: String
143    )
144
145    // RentalSettled
146    // This Rental was not returned in time, its deposit has been distributed
147    // to the original owner of the rented asset and to the assets'
148    // royalty beneficiaries
149    //
150    access(all) event RentalSettled(
151        rentalResourceID: UInt64, 
152        listingResourceID: UInt64,
153        renter: Address,
154        lender: Address,
155        nftID: UInt64,
156        nftType: String,
157        deposit: UFix64
158    )
159
160    access(all) let FlowtyRentalsStorefrontStoragePath: StoragePath
161    access(all) let FlowtyRentalsMarketplaceStoragePath: StoragePath
162    access(all) let FlowtyRentalsStorefrontPublicPath: PublicPath
163    access(all) let FlowtyRentalsMarketplacePublicPath: PublicPath
164    access(all) let FlowtyRentalsAdminStoragePath: StoragePath
165
166    // SuspendedFundingPeriod
167    // A period of time in seconds before which
168    // a listing cannot be rented.
169    access(all) var SuspendedFundingPeriod: UFix64
170
171    // Fee
172    // Fee taken as a percentage of the initial rental fee.
173    // For instance, if a rental has a fee of 10, Flowty will take a fee
174    // of 10 * Fee where Fee is less than 1.
175    access(all) var Fee: UFix64
176
177    // ListingDetails
178    // Non-resource data about a listing.
179    //
180    access(all) struct ListingDetails {
181        // The ID of the storefront that this listing was made on.
182        // Note that this listing cannot be transferred to another address
183        access(all) var flowtyStorefrontID: UInt64
184
185        // Signal indicating whether the listing has been rented or not.
186        // It can only be rented once.
187        access(all) var rented: Bool
188
189        // The type of the nft being rented. When attempting to rent an nft,
190        // a listing will check that the type of the nft being rented
191        // matches nftType
192        //
193        access(all) let nftType: Type
194
195        // The ID of the nft being rented. When attempting to rent an nft,
196        // a listing will check that the ID of the nft being rented
197        // matched nftID
198        access(all) let nftID: UInt64
199
200        // The flat fee amount collected. This amount is split when a listing is
201        // rented to the owner of the listing, flowty, and royalty owners
202        //
203        access(all) let amount: UFix64
204
205        // The deposit fee collected. This amount is returned to a renter
206        // if they return the nft they rented. However, if they do not,
207        // the deposit will be transferred to the original owner of the nft
208        // and to the royalty recipient of the nft collection
209        //
210        access(all) let deposit: UFix64
211
212        // The number of seconds that a rental is valid for when it is rented
213        // After the term has elapsed, a rental can be settled.
214        access(all) var term: UFix64
215
216        // The type of FungibleToken to be accepted as payment.
217        access(all) let paymentVaultType: Type
218
219        // A flag to reenable the listing once it has been returned
220        // This allows a user to keep a listing open in perpetuity
221        // should they desire to leave it open.
222        access(all) var reenableOnReturn: Bool
223
224        // Contains information about how to pay owner of this listing
225        access(self) let paymentCut: Flowty.PaymentCut
226
227        // The time that this listing was created
228        access(all) let listedTime: UFix64
229
230        // The royalty percentage taken at the time of listing.
231        access(all) var royaltyRate: UFix64
232
233        // The duration that this listing is valid for. If this amount of time
234        // has passed, the listing can no-longer be rented.
235        access(all) var expiresAfter: UFix64
236
237        // An optional parameter that can be used to prevent all addresses
238        // except for the specified one to rent this listing.
239        // This is how we achieve private listings.
240        access(all) var renter: Address?
241
242        access(all) view fun getPaymentCut(): Flowty.PaymentCut {
243            return self.paymentCut
244        }
245
246        // getTotalPayment
247        // get the total amount needed to rent this listing.
248        access(all) view fun getTotalPayment(): UFix64 {
249            return self.amount + self.deposit
250        }
251
252        access(contract) fun setToRented() {
253            self.rented = true
254        }
255
256        init (
257            nftType: Type,
258            nftID: UInt64,
259            amount: UFix64,
260            deposit: UFix64,
261            term: UFix64,
262            paymentVaultType: Type,
263            storefrontID: UInt64,
264            paymentCut: Flowty.PaymentCut,
265            expiresAfter: UFix64,
266            renter: Address?,
267            royaltyRate: UFix64
268        ) {
269            assert(paymentCut.amount > 0.0, message: "Listing must have non-zero requested amount")
270            
271            self.flowtyStorefrontID = storefrontID
272            self.rented = false
273            self.nftType = nftType
274            self.nftID = nftID
275            self.amount = amount
276            self.deposit = deposit
277            self.term = term
278            self.paymentVaultType = paymentVaultType
279            self.listedTime = getCurrentBlock().timestamp
280            self.royaltyRate = royaltyRate
281            self.expiresAfter = expiresAfter
282            self.paymentCut = paymentCut
283            self.renter = renter
284            self.reenableOnReturn = false
285        }
286    }
287
288    // ListingPublic
289    // An inerface providing a useful public interface to a listing
290    //
291    access(all) resource interface ListingPublic {
292        // borrowNFT
293        // This will assert in the same way as the NFT standard borrowNFT()
294        // if the NFT is absent, for example if it has been sold via another listing.
295        //
296        access(all) view fun borrowNFT(): &{NonFungibleToken.NFT}?
297
298        // rent
299        // Rent the listing. Distributing fees to all parties and taking a deposit to be held
300        // until the nft is either returned or the rental defaults. A rental can be automatically returned
301        // if renterNFTProvider is provided, giving us a way obtain the the rented nft automatically
302        // to be returned.
303        access(all) fun rent(
304            payment: @{FungibleToken.Vault},
305            renterFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>,
306            renterNFTCollection: Capability<&{NonFungibleToken.CollectionPublic}>,
307            renterNFTProvider: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.CollectionPublic, NonFungibleToken.Provider}>?
308        )
309
310        access(all) view fun getDetails(): ListingDetails
311
312        // suspensionTimeRemaining
313        // returns the amount of time left until a listing can be filled.
314        //
315        access(all) view fun suspensionTimeRemaining(): Fix64
316
317        // remainingTimeToRent
318        // returns the amount of time left until this listing is no longer valid
319        //
320        access(all) view fun remainingTimeToRent(): Fix64
321
322        // isRentingEnabled
323        // checks if a listing can be rented or not.
324        //
325        access(all) view fun isRentingEnabled(): Bool
326    }
327
328    // Listing
329    // A resource that allows an NFT to be temporarily owned by another account in exchange
330    // for a Fee and a Deposit.
331    access(all) resource Listing: ListingPublic, FlowtyListingCallback.Listing, Burner.Burnable {
332        access(all) event ResourceDestroyed(
333            listingResourceID: UInt64 = self.uuid,
334            flowtyStorefrontID: UInt64 = self.details.flowtyStorefrontID,
335            funded: Bool = self.details.rented,
336            nftID: UInt64 = self.details.nftID,
337            nftType: String = self.details.nftType.identifier,
338            flowtyStorefrontAddress: Address = self.nftPublicCollectionCapability.address
339        )
340
341        // The simple (non-Capability, non-complex) details of the listing
342        access(self) let details: ListingDetails
343
344        // A capability allowing this resource to withdraw the NFT with the given ID from its collection.
345        // This capability allows the resource to withdraw *any* NFT, so you should be careful when giving
346        // such a capability to a resource and always check its code to make sure it will use it in the
347        // way that it claims.
348        access(contract) let nftProviderCapability: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>
349
350        // A capability allowing this resource to access the owner's NFT public collection
351        access(contract) let nftPublicCollectionCapability: Capability<&{NonFungibleToken.CollectionPublic}>
352
353        // reference to the owner's fungibleTokenReceiver to pay them in the event of a settlement.
354        access(contract) let ownerFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>
355
356        // borrowNFT
357        // This will assert in the same way as the NFT standard borrowNFT()
358        // if the NFT is absent, for example if it has been sold via another listing.
359        //
360        access(all) view fun borrowNFT(): &{NonFungibleToken.NFT}? {
361            pre {
362                self.nftProviderCapability.check(): "provider capability failed check"
363            }
364
365            if let ref = self.nftProviderCapability.borrow()!.borrowNFT(self.getDetails().nftID) {
366                if ref.getType() != self.details.nftType || ref.id != self.details.nftID {
367                    return nil
368                }
369
370                return ref
371            }
372
373            return nil
374        }
375
376        // getDetails
377        // Get the details of the current state of the Listing as a struct.
378        // This avoids having more public variables and getter methods for them, and plays
379        // nicely with scripts (which cannot return resources).
380        //
381        access(all) view fun getDetails(): ListingDetails {
382            return self.details
383        }
384
385        // rent
386        // Rent the listing. Distributing fees to all parties and taking a deposit to be held
387        // until the nft is either returned or the rental defaults. A rental can be automatically returned
388        // if renterNFTProvider is provided, giving us a way obtain the the rented nft automatically
389        // to be returned.
390        access(all) fun rent(
391            payment: @{FungibleToken.Vault},
392            renterFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>,
393            renterNFTCollection: Capability<&{NonFungibleToken.CollectionPublic}>,
394            renterNFTProvider: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.CollectionPublic, NonFungibleToken.Provider}>?
395        ) {
396            pre {
397                self.isRentingEnabled(): "Renting is not enabled or this listing has expired"
398                !self.details.rented: "listing has already been rented"
399                payment.getType() == self.details.paymentVaultType: "payment vault is not requested fungible token"
400                payment.balance == self.details.getTotalPayment(): "payment vault does not contain requested amount"
401                self.nftProviderCapability.check(): "nftProviderCapability failed check"
402                renterNFTCollection.check(): "renterNFTCollection failed check"
403                renterNFTProvider == nil || renterNFTProvider!.check(): "renterNFTProvider failed check"
404            }
405
406            // handle if this listing is private or not.
407            if self.details.renter != nil {
408                assert(renterNFTCollection.address == self.details.renter, message: "incorrect renter address on renterNFTCollection")
409                assert(renterFungibleTokenReceiver.address == self.details.renter, message: "incorrect renter address")
410            }
411
412            self.details.setToRented()
413
414            // withdraw the NFT being rented and ensure that its type and id match the listing.
415            // This protects the renter from receiving the wrong nft.
416            let nft <- self.nftProviderCapability.borrow()!.withdraw(withdrawID: self.details.nftID)
417            let ref = &nft as &{NonFungibleToken.NFT}
418            assert(nft.getType() == self.details.nftType, message: "withdrawn NFT is not of specified type")
419            assert(nft.id == self.details.nftID, message: "withdrawn NFT does not have specified ID")
420            assert(FlowtyUtils.isSupported(ref), message: "nft type is not supported")
421
422            let royalties = RoyaltiesLedger.get(self.uuid)
423            
424            // set the deposit aside and calculate fee payouts for the rest
425            let depositCut <- payment.withdraw(amount: self.details.deposit)
426            assert(payment.balance == self.details.amount, message: "balance after deposit does not match rental amount")
427
428            // create the Rental resource on the Flowty accounts' marketplace
429            let listingResourceID = self.uuid
430            let marketplace = FlowtyRentals.borrowMarketplace()
431            let rental = marketplace.createRental(
432                storefrontID: self.details.flowtyStorefrontID,
433                listingResourceID: listingResourceID,
434                nftID: self.details.nftID,
435                nftType: self.details.nftType,
436                paymentVaultType: self.details.paymentVaultType,
437                term: self.details.term,
438                listingDetails: self.details,
439                ownerNFTCollectionPublic: self.nftPublicCollectionCapability,
440                ownerFungibleTokenReceiver: self.ownerFungibleTokenReceiver,
441                renterFungibleTokenReceiver: renterFungibleTokenReceiver,
442                depositedFungibleTokens: <-depositCut,
443                renterNFTCollection: renterNFTCollection,
444                renterNFTProvider: renterNFTProvider
445            )
446
447            let details = rental.getDetails()
448            let expiration = details.term + details.startTime
449            // September 3rd 12:00 AM
450            assert(payment.getType().identifier != "A.b19436aae4d94622.FiatToken.Vault" || expiration < 1725321600.0, message: "rentals are not able to be filled that expire on or after September 3rd 12:00 AM GMT.")
451
452            if let callback = FlowtyRentals.borrowCallbackContainer() {
453                callback.handle(stage: FlowtyListingCallback.Stage.Filled, listing: &self as &{FlowtyListingCallback.Listing}, nft: ref)
454                callback.handle(stage: FlowtyListingCallback.Stage.Created, listing: rental, nft: ref)
455            }
456
457            // transfer the nft being rented into the renter's nft collection
458            let renterCollectionCap = renterNFTCollection.borrow()!
459            renterCollectionCap.deposit(token: <-nft)
460
461            // Calculate fee amounts. First we calculate the amount sent to Flowty
462            // and to the royalty recipient. The remainder goes to the listing owner.
463            let flowtyFeeAmount = payment.balance * FlowtyRentals.Fee
464            let royaltyFeeAmount = payment.balance * self.details.royaltyRate
465
466            // withdraw the cuts going to flowty and our royalty recipient into their own vaults
467            let flowtyFeeCut <- payment.withdraw(amount: flowtyFeeAmount)           
468            let royaltyFeeCut <- payment.withdraw(amount: royaltyFeeAmount)
469
470            let tokenInfo = FlowtyUtils.getTokenInfo(self.details.paymentVaultType)!
471            let royaltyTokenPath = tokenInfo.receiverPath
472            let depositor = FlowtyRentals.account.storage.borrow<auth(LostAndFound.Deposit) &LostAndFound.Depositor>(from: LostAndFound.DepositorStoragePath)!
473
474            if royalties != nil {
475                let tokenInfo = FlowtyUtils.getTokenInfo(self.details.paymentVaultType)!
476                let royaltyCuts = FlowtyUtils.metadataRoyaltiesToRoyaltyCuts(tokenInfo: tokenInfo, mdRoyalties: [royalties!])
477                if let leftover <- FlowtyUtils.distributeRoyaltiesWithDepositor(royaltyCuts: royaltyCuts, depositor: depositor, vault: <-royaltyFeeCut) {
478                    payment.deposit(from: <-leftover)  
479                }
480            } else {
481                payment.deposit(from: <-royaltyFeeCut)
482            }
483
484            // get payment cut information for the listing owner to receive payment for
485            // this listing being rented.
486            assert(self.details.getPaymentCut().receiver.check(), message: "paymentCut receiver failed check")
487            let receiver = self.details.getPaymentCut().receiver.borrow()!
488            receiver.deposit(from: <-payment)
489
490            // get the path and corresponding capability to send fees to the Flowty account
491            let flowtyFeeReceiver = FlowtyRentals.account.capabilities.get<&{FungibleToken.Receiver}>(tokenInfo.receiverPath)!.borrow()!
492            flowtyFeeReceiver.deposit(from: <-flowtyFeeCut)
493
494            // check if automatic return has been enabled by renter.
495            let enabledAutomaticReturn = renterNFTProvider != nil
496
497            emit ListingRented(
498                flowtyStorefrontAddress: self.owner!.address,
499                flowtyStorefrontID: self.details.flowtyStorefrontID,
500                renterAddress: renterNFTCollection.address,
501                listingResourceID: listingResourceID,
502                rentalResourceID: rental.uuid,
503                nftID: self.details.nftID,
504                nftType: self.details.nftType.identifier,
505                amount: self.details.amount,
506                deposit: self.details.deposit,
507                enabledAutomaticReturn: enabledAutomaticReturn
508            )
509        }
510        
511        // suspensionTimeRemaining
512        // returns the amount of time left until a listing can be filled.
513        //
514        access(all) view fun suspensionTimeRemaining() : Fix64 {
515            let listedTime = self.details.listedTime
516            let currentTime = getCurrentBlock().timestamp
517
518            let remaining = Fix64(listedTime + Flowty.SuspendedFundingPeriod) - Fix64(currentTime)
519
520            return remaining
521        }
522
523        // remainingTimeToRent
524        // returns the amount of time left until this listing is no longer valid
525        //
526        access(all) view fun remainingTimeToRent(): Fix64 {
527            let listedTime = self.details.listedTime
528            let currentTime = getCurrentBlock().timestamp
529            let remaining = Fix64(listedTime + self.details.expiresAfter) - Fix64(currentTime)
530            return remaining
531        }
532
533        // isRentingEnabled
534        // checks if a listing can be rented or not.
535        //
536        access(all) view fun isRentingEnabled(): Bool {
537            let timeRemaining = self.suspensionTimeRemaining()
538            let listingTimeRemaining = self.remainingTimeToRent()
539            return timeRemaining < Fix64(0.0) && listingTimeRemaining > Fix64(0.0)
540        }
541
542        init (
543            nftProviderCapability: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>,
544            nftPublicCollectionCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
545            ownerFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>,
546            nftType: Type,
547            nftID: UInt64,
548            amount: UFix64,
549            deposit: UFix64,
550            term: UFix64,
551            paymentVaultType: Type,
552            paymentCut: Flowty.PaymentCut,
553            storefrontID: UInt64,
554            expiresAfter: UFix64,
555            renter: Address?
556        ) {
557            pre {
558                nftProviderCapability.check(): "nftProviderCapability failed check"
559                nftPublicCollectionCapability.check(): "nftPublicCollectionCapability failed check"
560                ownerFungibleTokenReceiver.check(): "ownerFungibleTokenReceiver failed check"
561            }
562
563            self.nftProviderCapability = nftProviderCapability
564            self.nftPublicCollectionCapability = nftPublicCollectionCapability
565            self.ownerFungibleTokenReceiver = ownerFungibleTokenReceiver
566
567            let provider = self.nftProviderCapability.borrow()!
568            let nft = provider.borrowNFT(nftID) ?? panic("could not borrow nft")
569            assert(nft.getType() == nftType, message: "token is not of specified type")
570            assert(nft.id == nftID, message: "token does not have specified ID")
571            let royaltyRate = FlowtyUtils.getRoyaltyRate(nft)
572
573            self.details = ListingDetails(
574                nftType: nftType,
575                nftID: nftID,
576                amount: amount,
577                deposit: deposit,
578                term: term,
579                paymentVaultType: paymentVaultType,
580                storefrontID: storefrontID,
581                paymentCut: paymentCut,
582                expiresAfter: expiresAfter,
583                renter: renter,
584                royaltyRate: royaltyRate
585            )
586
587            if let callback = FlowtyRentals.borrowCallbackContainer() {
588                callback.handle(stage: FlowtyListingCallback.Stage.Created, listing: &self as &{FlowtyListingCallback.Listing}, nft: nft)
589            }
590        }
591
592        access(contract) fun burnCallback() {
593            FlowtyRentals.borrowRoyaltiesLedger().remove(self.uuid)
594
595            emit ListingDestroyed(
596                flowtyStorefrontAddress: self.ownerFungibleTokenReceiver.address,
597                flowtyStorefrontID: self.details.flowtyStorefrontID,
598                listingResourceID: self.uuid,
599                nftID: self.details.nftID,
600                nftType: self.details.nftType.identifier
601            )
602        }
603    }
604
605    // Rental Details
606    // A struct containing a Rental's non-resource data.
607    access(all) struct RentalDetails {
608        // |-- gathered by an initialized Rental resource --|
609        // The storefront which owns this rental.
610        access(all) var flowtyStorefrontID: UInt64
611        // The listing being funded
612        access(all) var listingResourceID: UInt64
613        // They type used for payment to obtain the rental.
614        // Tokens taken as a deposit are also made of this type
615        access(all) var paymentVaultType: Type
616
617        // The number of seconds that this rental is good for.
618        // If the rental is not returned before this time, the deposit can be revoked
619        access(all) var term: UFix64
620
621        // The id of the nft that was rented. This is the same as listingDetails.nftID
622        access(all) var nftID: UInt64
623
624        // The type of the nft that was rented. This is the same as listingDetails.nftType
625        access(all) var nftType: Type
626
627        // |-- The below variables are maintained by our smart contract --|
628        access(all) var returned: Bool
629        access(all) var settled: Bool
630
631        // The time this Rental was created
632        access(all) var startTime: UFix64
633
634        access(contract) fun setToReturned() {
635            self.returned = true
636        }
637
638        access(contract) fun setToSettled() {
639            self.settled = true
640        }
641
642        init (
643            flowtyStorefrontID: UInt64,
644            listingResourceID: UInt64,
645            paymentVaultType: Type,
646            nftType: Type,
647            nftID: UInt64,
648            term: UFix64
649        ) {
650            self.flowtyStorefrontID = flowtyStorefrontID
651            self.listingResourceID = listingResourceID
652            self.nftType = nftType
653            self.nftID = nftID
654            self.term = term
655            self.paymentVaultType = paymentVaultType
656
657            self.startTime = getCurrentBlock().timestamp
658            self.returned = false
659            self.settled = false
660        }
661    }
662
663    // RentalPublic
664    // An interface providing a useful public interface to a Rental.
665    //
666    access(all) resource interface RentalPublic {
667        // The entry point method to return a rental.
668        // The same NFT as the one that was rented must be returned.
669        access(all) fun returnNFT(nft: @{NonFungibleToken.NFT})
670
671        // Return the details of this Rental
672        access(all) view fun getDetails(): RentalDetails
673
674        // Return the listingDetails that were used to create this rental
675        access(all) view fun getListingDetails(): FlowtyRentals.ListingDetails
676
677        // How much time is left until this Rental has expired
678        access(all) view fun timeRemaining() : Fix64
679
680        // Whether this rental has expired and can be settled
681        access(all) view fun isRentalExpired(): Bool
682
683        access(all) fun settleRental()
684    }
685
686    // The resource used to represent a Rental.
687    // A Rental contains the deposit held in exchange for an NFT
688    // If that same NFT is returned, the deposit is released back to the renter.
689    access(all) resource Rental: RentalPublic, FlowtyListingCallback.Listing, Burner.Burnable {
690        access(all) event ResourceDestroyed(flowtyStorefrontID: UInt64 = self.details.flowtyStorefrontID, rentalResourceID: UInt64 = self.uuid, listingResourceID: UInt64 = self.details.listingResourceID)
691
692        // The non-resource data of a Rental
693        access(self) let details: RentalDetails
694
695        // The details of the listing that was rented
696        access(self) let listingDetails: ListingDetails
697
698        // Tokens held as a deposit
699        access(contract) var depositedFungibleTokens: @{FungibleToken.Vault}?
700
701        // reference to the original owner of the nft which was rented
702        access(contract) let ownerNFTCollectionPublic: Capability<&{NonFungibleToken.CollectionPublic}>
703        // reference to pay the original owner in the event that the rented NFT is not returned
704        access(contract) let ownerFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>
705
706        // Used to return the deposit held when the Rental was made
707        access(contract) let renterFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>
708
709        // Capability used to transfer the rented nft to our renter
710        access(contract) let renterNFTCollection: Capability<&{NonFungibleToken.CollectionPublic}>
711
712        // optional capability to automatically return the asset on settlement
713        access(contract) let renterNFTProvider: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.CollectionPublic, NonFungibleToken.Provider}>?
714
715        init (
716            // Rental details
717            storefrontID: UInt64,
718            listingResourceID: UInt64,
719            paymentVaultType: Type,
720            term: UFix64,
721            nftID: UInt64,
722            nftType: Type,
723            listingDetails: ListingDetails,
724
725            // Rental resource
726            ownerNFTCollectionPublic: Capability<&{NonFungibleToken.CollectionPublic}>,
727            renterFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>,
728            ownerFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>,
729            depositedFungibleTokens: @{FungibleToken.Vault}?,
730            renterNFTCollection: Capability<&{NonFungibleToken.CollectionPublic}>,
731            renterNFTProvider: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.CollectionPublic, NonFungibleToken.Provider}>?
732        ) {
733            self.ownerNFTCollectionPublic = ownerNFTCollectionPublic
734            self.renterFungibleTokenReceiver = renterFungibleTokenReceiver
735            self.ownerFungibleTokenReceiver = ownerFungibleTokenReceiver
736            self.depositedFungibleTokens <- depositedFungibleTokens
737            self.renterNFTProvider = renterNFTProvider
738
739            self.renterNFTCollection = renterNFTCollection
740            self.listingDetails = listingDetails
741
742            self.details = RentalDetails(
743                flowtyStorefrontID: storefrontID,
744                listingResourceID: listingResourceID,
745                paymentVaultType: paymentVaultType,
746                nftType: nftType,
747                nftID: nftID,
748                term: term
749            )
750        }
751
752        access(all) view fun getDetails(): RentalDetails {
753            return self.details
754        }
755
756        access(all) view fun getListingDetails(): ListingDetails {
757            return self.listingDetails
758        }
759
760        // The entry point method to return a rental.
761        // The same NFT as the one that was rented must be returned.
762        access(all) fun returnNFT(nft: @{NonFungibleToken.NFT}) {
763            pre {
764                !self.isRentalExpired(): "rental has expired"
765                !self.details.returned: "rental has been returned"
766                nft.getType() == self.details.nftType: "returned nft has the wrong type"
767                nft.id == self.details.nftID: "incorrect nftID"
768            }
769
770            // withdraw the deposited tokens when the rental was created
771            let deposit <- self.depositedFungibleTokens <- nil
772
773            self.details.setToReturned()
774
775            let depositor = FlowtyRentals.account.storage.borrow<auth(LostAndFound.Deposit) &LostAndFound.Depositor>(from: LostAndFound.DepositorStoragePath)!
776
777            if let callback = FlowtyRentals.borrowCallbackContainer() {
778                callback.handle(stage: FlowtyListingCallback.Stage.Completed, listing: &self as &{FlowtyListingCallback.Listing}, nft: &nft as &{NonFungibleToken.NFT})
779            }
780
781            // get the fungibleToken vault capable of receiving the deposit so it can be returned
782            FlowtyUtils.trySendFungibleTokenVault(vault: <-deposit!, receiver: self.renterFungibleTokenReceiver, depositor: depositor)
783
784            // return the nft to the owner's collection
785            FlowtyUtils.trySendNFT(nft: <-nft, receiver: self.ownerNFTCollectionPublic, depositor: depositor)
786            
787            FlowtyRentals.borrowRoyaltiesLedger().remove(self.details.listingResourceID)
788
789            emit RentalReturned(
790                flowtyStorefrontAddress: self.ownerFungibleTokenReceiver.address,
791                flowtyStorefrontID: self.details.flowtyStorefrontID,
792                renterAddress: self.renterFungibleTokenReceiver.address,
793                listingResourceID: self.details.listingResourceID,
794                rentalResourceID: self.uuid,
795                nftID: self.details.nftID,
796                nftType: self.details.nftType.identifier
797            )
798        }
799
800        // settleRental can only be executed after the rental has expired.
801        // If the Renter supplied an optional provider which we can withdraw the NFT with,
802        // Then we will attempt to automatically take the NFT back. If this is possible, the deposit will
803        // be returned to the renter.
804        access(all) fun settleRental() {
805            pre {
806                self.isRentalExpired(): "rental hasn't expired"
807                self.details.returned == false: "rental has already been returned"
808                self.details.settled == false: "rental has already been settled"
809            }
810
811            let depositor = FlowtyRentals.account.storage.borrow<auth(LostAndFound.Deposit) &LostAndFound.Depositor>(from: LostAndFound.DepositorStoragePath)!
812
813            // check if we can automatically return the NFT
814            // do we have a provider?
815            if self.renterNFTProvider != nil {
816                // borrow it.
817                if self.renterNFTProvider!.check() {
818                    let renterNFTProvider = self.renterNFTProvider!.borrow()!
819                    // does this NFT Collection have the ID that we need to withdraw?
820                    let ids = renterNFTProvider.getIDs()
821                    if ids.contains(self.details.nftID) {
822                        let borrowedNFT = renterNFTProvider.borrowNFT(self.details.nftID)
823                        if borrowedNFT != nil && borrowedNFT!.getType() == self.details.nftType && borrowedNFT!.id == self.details.nftID {
824                            let nft <- renterNFTProvider.withdraw(withdrawID: self.details.nftID)
825                            if nft.getType() == self.details.nftType && nft.id == self.details.nftID {
826
827                                if let callback = FlowtyRentals.borrowCallbackContainer() {
828                                    callback.handle(stage: FlowtyListingCallback.Stage.Completed, listing: &self as &{FlowtyListingCallback.Listing}, nft: &nft as &{NonFungibleToken.NFT})
829                                }
830
831                                FlowtyUtils.trySendNFT(nft: <-nft, receiver: self.ownerNFTCollectionPublic, depositor: depositor)
832
833                                let deposit <- self.depositedFungibleTokens <- nil
834                                FlowtyUtils.trySendFungibleTokenVault(vault: <-deposit!, receiver: self.renterFungibleTokenReceiver, depositor: depositor)
835
836                                // it worked! the nft is returned
837                                self.details.setToReturned()
838
839                                FlowtyRentals.borrowRoyaltiesLedger().remove(self.details.listingResourceID)
840
841                                emit RentalReturned(
842                                    flowtyStorefrontAddress: self.ownerFungibleTokenReceiver.address,
843                                    flowtyStorefrontID: self.details.flowtyStorefrontID,
844                                    renterAddress: self.renterFungibleTokenReceiver.address,
845                                    listingResourceID: self.details.listingResourceID,
846                                    rentalResourceID: self.uuid,
847                                    nftID: self.details.nftID,
848                                    nftType: self.details.nftType.identifier
849                                )
850                                return
851                            } else {
852                                // malicious actor has some weird NFT they're trying to send. we return it to them
853                                // this path should only be able to be reached intentionally. At that point, we won't know if the
854                                // receiver is setup properly to receive the borrowed item back or not so we should just make a lostandfound 
855                                // item for them and move on. If they can mess with a borrowed item returning a type that is different than 
856                                // the actual withdrawn nft, they can handle redeeming their item back from the lostandfound.
857
858                                FlowtyUtils.depositToLostAndFound(
859                                    redeemer: self.renterNFTCollection.address,
860                                    item: <- nft,
861                                    memo: nil,
862                                    display: nil,
863                                    depositor: depositor
864                                )
865                            }
866                        }
867                    }
868                }
869            }
870
871            // we couldn't return the nft, settle it.
872            self.details.setToSettled()
873
874            if self.depositedFungibleTokens != nil {
875                // calculate the amounts to send to the owner and royalty
876                let vault <- self.depositedFungibleTokens <- nil
877                let amount = vault?.balance ?? panic("nil vault")
878                let royaltyFeeAmount = amount * self.listingDetails.royaltyRate
879
880                // get the vaults for payment
881                let v <- vault!
882
883                let tokenInfo = FlowtyUtils.getTokenInfo(self.details.paymentVaultType)!
884                let royaltyTokenPath = tokenInfo.receiverPath
885
886                let royalties = RoyaltiesLedger.get(self.details.listingResourceID)
887                if royalties != nil {
888                    let tokenInfo = FlowtyUtils.getTokenInfo(self.details.paymentVaultType)!
889                    let royaltyCuts = FlowtyUtils.metadataRoyaltiesToRoyaltyCuts(tokenInfo: tokenInfo, mdRoyalties: [royalties!])
890
891                    let royaltyFeeCut <- v.withdraw(amount: royaltyFeeAmount)
892                    if let leftover <- FlowtyUtils.distributeRoyaltiesWithDepositor(royaltyCuts: royaltyCuts, depositor: depositor, vault: <-royaltyFeeCut) {
893                        v.deposit(from: <-leftover)
894                    }
895                }
896
897                // distribute the rest to the original owner
898                FlowtyUtils.trySendFungibleTokenVault(vault: <-v, receiver: self.listingDetails.getPaymentCut().receiver, depositor: depositor)
899            }
900
901            FlowtyRentals.borrowRoyaltiesLedger().remove(self.details.listingResourceID)
902
903            if let callback = FlowtyRentals.borrowCallbackContainer() {
904                callback.handle(stage: FlowtyListingCallback.Stage.Completed, listing: &self as &{FlowtyListingCallback.Listing}, nft: nil)
905            }
906
907            emit RentalSettled(
908                rentalResourceID: self.uuid, 
909                listingResourceID: self.details.listingResourceID,
910                renter: self.renterFungibleTokenReceiver.address,
911                lender: self.ownerFungibleTokenReceiver.address,
912                nftID: self.details.nftID,
913                nftType: self.details.nftType.identifier,
914                deposit: self.listingDetails.deposit
915            )
916        }
917
918        access(contract) fun burnCallback() {
919            pre {
920                self.details.settled || self.details.returned: "rental must be returned or settled to be destroyed"
921                self.depositedFungibleTokens == nil || self.depositedFungibleTokens?.balance == 0.0: "deposit balance is not 0"
922            }
923        }
924
925        // how much time is left to return this rental
926        access(all) view fun timeRemaining() : Fix64 {
927            let rentalTerm = self.details.term
928            let startTime = self.details.startTime
929            let currentTime = getCurrentBlock().timestamp
930            let remaining = Fix64(startTime + rentalTerm) - Fix64(currentTime)
931            return remaining
932        }
933
934        access(all) view fun isRentalExpired() : Bool {
935            return self.timeRemaining() < Fix64(0.0)
936        }
937    }
938
939    // FlowtyRentalsMarketplaceManager
940    // An interface for adding and removing rental resources within a FlowtyRentalsMarketplace,
941    //
942    access(all) resource interface FlowtyRentalsMarketplaceManager {
943        access(all) event ResourceDestroyed(id: UInt64 = self.uuid)
944
945        // createFunding
946        // Allows the FlowtyRentalsMarketplace owner to create and insert Fundings.
947        //
948        access(contract) fun createRental(
949            storefrontID: UInt64, 
950            listingResourceID: UInt64,
951            nftID: UInt64,
952            nftType: Type,
953            paymentVaultType: Type,
954            term: UFix64,
955            listingDetails: ListingDetails,
956            ownerNFTCollectionPublic: Capability<&{NonFungibleToken.CollectionPublic}>,
957            ownerFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>,
958            renterFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>,
959            depositedFungibleTokens: @{FungibleToken.Vault}?,
960            renterNFTCollection: Capability<&{NonFungibleToken.CollectionPublic}>,
961            renterNFTProvider: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.CollectionPublic, NonFungibleToken.Provider}>?
962        ): &Rental
963        // removeRental
964        // Allows the FlowtyRentalsMarketplace owner to remove any rental resource.
965        //
966        access(Administrator) fun removeRental(rentalResourceID: UInt64)
967    }
968
969    access(all) resource interface FlowtyRentalsMarketplacePublic {
970        access(all) view fun getRentalIDs(): [UInt64]
971        access(all) view fun borrowRental(rentalResourceID: UInt64): &{RentalPublic}?
972    }
973
974    access(all) resource FlowtyRentalsMarketplace: FlowtyRentalsMarketplaceManager, FlowtyRentalsMarketplacePublic, Burner.Burnable {
975        access(all) event ResourceDestroyed(flowtyStorefrontID: UInt64 = self.uuid)
976
977        access(self) var rentals: @{UInt64: Rental}
978
979        // create a rental and store it on our contract marketplace
980        access(contract) fun createRental(
981            storefrontID: UInt64, 
982            listingResourceID: UInt64,
983            nftID: UInt64,
984            nftType: Type,
985            paymentVaultType: Type,
986            term: UFix64,
987            listingDetails: ListingDetails,
988            ownerNFTCollectionPublic: Capability<&{NonFungibleToken.CollectionPublic}>,
989            ownerFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>,
990            renterFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>,
991            depositedFungibleTokens: @{FungibleToken.Vault}?,
992            renterNFTCollection: Capability<&{NonFungibleToken.CollectionPublic}>,
993            renterNFTProvider: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.CollectionPublic, NonFungibleToken.Provider}>?
994         ): &Rental {
995            let renter = renterFungibleTokenReceiver.address
996            let owner = ownerNFTCollectionPublic.address
997
998            // Create funding resource
999            let rental <- create Rental(
1000                storefrontID: storefrontID,
1001                listingResourceID: listingResourceID,
1002                paymentVaultType: paymentVaultType,
1003                term: term,
1004                nftID: nftID,
1005                nftType: nftType,
1006                listingDetails: listingDetails,
1007                ownerNFTCollectionPublic: ownerNFTCollectionPublic,
1008                renterFungibleTokenReceiver: renterFungibleTokenReceiver,
1009                ownerFungibleTokenReceiver: ownerFungibleTokenReceiver,
1010                depositedFungibleTokens: <-depositedFungibleTokens,
1011                renterNFTCollection: renterNFTCollection,
1012                renterNFTProvider: renterNFTProvider
1013            )
1014
1015            let rentalResourceID = rental.uuid
1016
1017            // set the rental item here so we can reference it later
1018            let oldRental <- self.rentals[rentalResourceID] <- rental
1019            
1020            // Note that oldRental will always be nil, but we have to handle it.
1021            destroy oldRental
1022            return (&self.rentals[rentalResourceID])!
1023        }
1024
1025        // removes a rental from our contract
1026        access(Administrator) fun removeRental(rentalResourceID: UInt64) {
1027            let rental <- self.rentals.remove(key: rentalResourceID)
1028                ?? panic("missing Rental")
1029
1030            if let callback = FlowtyRentals.borrowCallbackContainer() {
1031                callback.handle(stage: FlowtyListingCallback.Stage.Destroyed, listing: &rental as &{FlowtyListingCallback.Listing}, nft: nil)
1032            }
1033    
1034            assert(rental.getDetails().returned == true || rental.getDetails().settled == true, message: "rental is not returned or settled")
1035
1036            Burner.burn(<-rental)
1037        }
1038
1039        access(all) view fun getRentalIDs(): [UInt64] {
1040            return self.rentals.keys
1041        }
1042
1043        access(all) view fun borrowRental(rentalResourceID: UInt64): &{RentalPublic}? {
1044            if self.rentals[rentalResourceID] != nil {
1045                return &self.rentals[rentalResourceID]
1046            } else {
1047                return nil
1048            }
1049        }
1050
1051        access(contract) fun burnCallback() {
1052            let ids = self.rentals.keys
1053            for id in ids {
1054                Burner.burn(<- self.rentals.remove(key: id)!)
1055            }
1056        }
1057
1058        init () {
1059            self.rentals <- {}
1060
1061            // Let event consumers know that this storefront exists
1062            emit FlowtyRentalsMarketplaceInitialized(flowtyRentalsMarketplaceResourceID: self.uuid)
1063        }
1064    }
1065
1066    access(all) resource interface FlowtyRentalsStorefrontManager {
1067        access(List) fun createListing(
1068            nftProviderCapability: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>,
1069            nftPublicCollectionCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
1070            ownerFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>,
1071            nftType: Type,
1072            nftID: UInt64,
1073            amount: UFix64,
1074            deposit: UFix64,
1075            term: UFix64,
1076            paymentVaultType: Type,
1077            paymentCut: Flowty.PaymentCut,
1078            expiresAfter: UFix64,
1079            renter: Address?
1080        ): UInt64
1081
1082        access(Cancel) fun removeListing(listingResourceID: UInt64)
1083    }
1084
1085    access(all) resource interface FlowtyRentalsStorefrontPublic {
1086        access(all) view fun getListingIDs(): [UInt64]
1087        access(all) view fun borrowListing(listingResourceID: UInt64): &{ListingPublic}?{
1088            post {
1089                result == nil || result!.getType() == Type<@Listing>()
1090            }
1091        }
1092        access(all) fun cleanup(listingResourceID: UInt64)
1093   }
1094
1095   // FlowtyRentalsStorefront -  The storefront which stores listing and provides functionality to fill them
1096   // A listing records the an nftid and type, a fee and deposit, and an optional address.
1097   access(all) resource FlowtyRentalsStorefront : FlowtyRentalsStorefrontManager, FlowtyRentalsStorefrontPublic, Burner.Burnable {
1098      access(all) event ResourceDestroyed(flowtyStorefrontID: UInt64 = self.uuid)
1099
1100       access(self) var listings: @{UInt64: Listing}
1101
1102       // create a new listing. Takes in a provider to withdraw the listed nft, and details
1103       // about the terms of the rental and ways to send out payment
1104       access(List) fun createListing(
1105            nftProviderCapability: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>,
1106            nftPublicCollectionCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
1107            ownerFungibleTokenReceiver: Capability<&{FungibleToken.Receiver}>,
1108            nftType: Type,
1109            nftID: UInt64,
1110            amount: UFix64,
1111            deposit: UFix64,
1112            term: UFix64,
1113            paymentVaultType: Type,
1114            paymentCut: Flowty.PaymentCut,
1115            expiresAfter: UFix64,
1116            renter: Address?
1117         ): UInt64 {
1118             pre {
1119                FlowtyUtils.isTokenSupported(type: paymentVaultType): "provided payment type is not supported"
1120                paymentCut.receiver.check() && paymentCut.receiver.borrow()!.getType() == paymentVaultType: "paymentCut receiver type and paymentVaultType do not match"
1121                nftProviderCapability.check(): "invalid nft provider"
1122            }
1123
1124            let ref = nftProviderCapability.borrow()!.borrowNFT(nftID) ?? panic("could not borrow nft")
1125            assert(FlowtyUtils.isSupported(ref), message: "nft type is not supported")
1126            let royalties = ref.resolveView(Type<MetadataViews.Royalties>()) as! MetadataViews.Royalties?
1127
1128            // create the listing
1129            let listing <- create Listing(
1130                nftProviderCapability: nftProviderCapability,
1131                nftPublicCollectionCapability: nftPublicCollectionCapability,
1132                ownerFungibleTokenReceiver: ownerFungibleTokenReceiver,
1133                nftType: nftType,
1134                nftID: nftID,
1135                amount: amount,
1136                deposit: deposit,
1137                term: term,
1138                paymentVaultType: paymentVaultType,
1139                paymentCut: paymentCut,
1140                storefrontID: self.uuid,
1141                expiresAfter: expiresAfter,
1142                renter: renter
1143            )
1144
1145            if royalties != nil {
1146                FlowtyRentals.borrowRoyaltiesLedger().set(listing.uuid, royalties!)
1147            }
1148
1149            let listingResourceID = listing.uuid
1150            let royaltyRate = listing.getDetails().royaltyRate
1151            let expiration = listing.getDetails().expiresAfter
1152
1153            // Add the new listing to the dictionary.
1154            let oldListing <- self.listings[listingResourceID] <- listing
1155            // Note that oldListing will always be nil, but we have to handle it.
1156            destroy oldListing
1157
1158            emit ListingAvailable(
1159                flowtyStorefrontAddress: self.owner!.address,
1160                flowtyStorefrontID: self.uuid,
1161                listingResourceID: listingResourceID,
1162                nftType: nftType.identifier,
1163                nftID: nftID,
1164                amount: amount,
1165                deposit: deposit,
1166                term: term,
1167                royaltyRate: royaltyRate,
1168                expiresAfter: expiration,
1169                paymentTokenType: paymentVaultType.identifier,
1170                renter: renter
1171            )
1172
1173            return listingResourceID
1174        }
1175
1176        access(Cancel) fun removeListing(listingResourceID: UInt64) {
1177            let listing <- self.listings.remove(key: listingResourceID)
1178                ?? panic("missing Listing")
1179            Burner.burn(<-listing)
1180        }
1181
1182        access(all) view fun getListingIDs(): [UInt64] {
1183            return self.listings.keys
1184        }
1185
1186        access(all) view fun borrowListing(listingResourceID: UInt64): &{ListingPublic}? {
1187            return &self.listings[listingResourceID]
1188        }
1189
1190        access(all) fun cleanup(listingResourceID: UInt64) {
1191            pre {
1192                self.listings[listingResourceID] != nil: "could not find listing with given id"
1193            }
1194
1195            let listing <- self.listings.remove(key: listingResourceID)!
1196            assert(listing.getDetails().rented == true, message: "listing is not rented, only admin can remove")
1197            Burner.burn(<-listing)
1198        }
1199
1200        access(contract) fun burnCallback() {
1201            for id in self.listings.keys {
1202                Burner.burn(<- self.listings.remove(key: id)!)
1203            }
1204
1205            // Let event consumers know that this storefront will no longer exist
1206            emit FlowtyRentalsStorefrontDestroyed(flowtyRentalsStorefrontResourceID: self.uuid)
1207        }
1208
1209        init () {
1210            self.listings <- {}
1211
1212            // Let event consumers know that this storefront exists
1213            emit FlowtyRentalsStorefrontInitialized(flowtyRentalsStorefrontResourceID: self.uuid)
1214        }
1215   }
1216
1217    // deprecated
1218    access(all) resource FlowtyAdmin {}
1219
1220    access(all) fun createStorefront(): @FlowtyRentalsStorefront {
1221        return <-create FlowtyRentalsStorefront()
1222    }
1223
1224    access(account) fun borrowMarketplace(): &FlowtyRentals.FlowtyRentalsMarketplace {
1225        return self.account.storage.borrow<&FlowtyRentals.FlowtyRentalsMarketplace>(from: FlowtyRentals.FlowtyRentalsMarketplaceStoragePath)!
1226    }
1227
1228    access(all) view fun borrowMarketplacePublic(): &{FlowtyRentalsMarketplacePublic} {
1229        let mp = self.account.storage.borrow<&{FlowtyRentalsMarketplacePublic}>(from: FlowtyRentals.FlowtyRentalsMarketplaceStoragePath)!
1230        return mp
1231    }
1232
1233    access(all) view fun borrowStorefrontPublic(addr: Address): &{FlowtyRentalsStorefrontPublic}? {
1234        return getAccount(addr).capabilities.get<&{FlowtyRentalsStorefrontPublic}>(FlowtyRentals.FlowtyRentalsStorefrontPublicPath).borrow()
1235    }
1236
1237    access(all) fun settleRental(rentalResourceID: UInt64){
1238        let marketplace = FlowtyRentals.borrowMarketplace()
1239        let rental = marketplace.borrowRental(rentalResourceID: rentalResourceID)
1240        rental!.settleRental()
1241    } 
1242
1243    access(all) resource FlowtyRentalsAdmin {
1244        access(Administrator) fun setFees(rentalFee: UFix64) {
1245            pre {
1246                rentalFee <= 1.0: "Funding fee should be a percentage"
1247            }
1248
1249            FlowtyRentals.Fee = rentalFee
1250        }
1251
1252        access(Administrator) fun setSuspendedFundingPeriod(period: UFix64) {
1253            FlowtyRentals.SuspendedFundingPeriod = period
1254        }
1255    }
1256
1257    access(contract) fun borrowCallbackContainer(): auth(FlowtyListingCallback.Handle) &FlowtyListingCallback.Container? {
1258        return self.account.storage.borrow<auth(FlowtyListingCallback.Handle) &FlowtyListingCallback.Container>(from: FlowtyListingCallback.ContainerStoragePath)
1259    }
1260
1261    access(contract) fun borrowRoyaltiesLedger(): auth(RoyaltiesLedger.Administrator) &RoyaltiesLedger.Ledger {
1262        return self.account.storage.borrow<auth(RoyaltiesLedger.Administrator) &RoyaltiesLedger.Ledger>(from: RoyaltiesLedger.StoragePath)!
1263    }
1264
1265    init () {
1266        self.FlowtyRentalsStorefrontStoragePath = /storage/FlowtyRentalsStorefront
1267        self.FlowtyRentalsStorefrontPublicPath = /public/FlowtyRentalsStorefront
1268        self.FlowtyRentalsMarketplaceStoragePath = /storage/FlowtyRentalsMarketplace
1269        self.FlowtyRentalsMarketplacePublicPath = /public/FlowtyRentalsMarketplace
1270        self.FlowtyRentalsAdminStoragePath = /storage/FlowtyRentalsAdmin
1271        
1272        self.Fee = 0.05 // Percentage of the rental amount taken as a fee
1273        self.SuspendedFundingPeriod = 1.0 // Period in seconds until the listing is valid
1274
1275        let marketplace <- create FlowtyRentalsMarketplace()
1276
1277        self.account.storage.save(<-marketplace, to: self.FlowtyRentalsMarketplaceStoragePath)
1278        let mpPubCap = self.account.capabilities.storage.issue<&{FlowtyRentals.FlowtyRentalsMarketplacePublic}>(FlowtyRentals.FlowtyRentalsMarketplaceStoragePath)
1279        self.account.capabilities.publish(mpPubCap, at: FlowtyRentals.FlowtyRentalsMarketplacePublicPath)
1280
1281        // FlowtyAdmin
1282        let flowtyAdmin <- create FlowtyRentalsAdmin()
1283        self.account.storage.save(<-flowtyAdmin, to: self.FlowtyRentalsAdminStoragePath)
1284
1285        emit FlowtyRentalsInitialized()
1286    }
1287}