Smart Contract

NFTStorefrontV2

A.4eb8a10cb9f87357.NFTStorefrontV2

Valid From

85,984,700

Deployed

1d ago
Feb 24, 2026, 11:58:38 PM UTC

Dependents

890646 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3import Burner from 0xf233dcee88fe0abe
4
5/// NFTStorefrontV2
6///
7/// A general purpose sale support contract for NFTs that implement the Flow NonFungibleToken standard.
8/// 
9/// Each account that wants to list NFTs for sale installs a Storefront,
10/// and lists individual sales within that Storefront as Listings.
11/// There is one Storefront per account, it handles sales of all NFT types
12/// for that account.
13///
14/// Each Listing can have one or more "cuts" of the sale price that
15/// goes to one or more addresses. Cuts can be used to pay listing fees
16/// or other considerations. 
17/// Each Listing can include a commission amount that is paid to whoever facilitates
18/// the purchase. The seller can also choose to provide an optional list of marketplace 
19/// receiver capabilities. In this case, the commission amount must be transferred to
20/// one of the capabilities in the list.
21///
22/// Each NFT may be listed in one or more Listings, the validity of each
23/// Listing can easily be checked.
24/// 
25/// Purchasers can watch for Listing events and check the NFT type and
26/// ID to see if they wish to buy the listed item.
27/// Marketplaces and other aggregators can watch for Listing events
28/// and list items of interest.
29///
30access(all) contract NFTStorefrontV2 {
31
32    access(all) entitlement CreateListing
33    access(all) entitlement RemoveListing
34
35    /// StorefrontInitialized
36    /// A Storefront resource has been created.
37    /// Event consumers can now expect events from this Storefront.
38    /// Note that we do not specify an address: we cannot and should not.
39    /// Created resources do not have an owner address, and may be moved
40    /// after creation in ways we cannot check.
41    /// ListingAvailable events can be used to determine the address
42    /// of the owner of the Storefront (...its location) at the time of
43    /// the listing but only at that precise moment in that precise transaction.
44    /// If the seller moves the Storefront while the listing is valid, 
45    /// that is on them.
46    ///
47    access(all) event StorefrontInitialized(storefrontResourceID: UInt64)
48
49    /// ListingAvailable
50    /// A listing has been created and added to a Storefront resource.
51    /// The Address values here are valid when the event is emitted, but
52    /// the state of the accounts they refer to may change outside of the
53    /// NFTStorefrontV2 workflow, so be careful to check when using them.
54    ///
55    access(all) event ListingAvailable(
56        storefrontAddress: Address,
57        listingResourceID: UInt64,
58        nftType: Type,
59        nftUUID: UInt64, 
60        nftID: UInt64,
61        salePaymentVaultType: Type,
62        salePrice: UFix64,
63        customID: String?,
64        commissionAmount: UFix64,
65        commissionReceivers: [Address]?,
66        expiry: UInt64
67    )
68
69    /// ListingCompleted
70    /// The listing has been resolved. It has either been purchased, removed or destroyed.
71    ///
72    access(all) event ListingCompleted(
73        listingResourceID: UInt64, 
74        storefrontResourceID: UInt64, 
75        purchased: Bool,
76        nftType: Type,
77        nftUUID: UInt64,
78        nftID: UInt64,
79        salePaymentVaultType: Type,
80        salePrice: UFix64,
81        customID: String?,
82        commissionAmount: UFix64,
83        commissionReceiver: Address?,
84        expiry: UInt64
85    )
86
87    /// UnpaidReceiver
88    /// A entitled receiver has not been paid during the sale of the NFT.
89    ///
90    access(all) event UnpaidReceiver(receiver: Address, entitledSaleCut: UFix64)
91
92    /// StorefrontStoragePath
93    /// The location in storage that a Storefront resource should be located.
94    access(all) let StorefrontStoragePath: StoragePath
95
96    /// StorefrontPublicPath
97    /// The public location for a Storefront link.
98    access(all) let StorefrontPublicPath: PublicPath
99
100
101    /// SaleCut
102    /// A struct representing a recipient that must be sent a certain amount
103    /// of the payment when a token is sold.
104    ///
105    access(all) struct SaleCut {
106        /// The receiver for the payment.
107        /// Note that we do not store an address to find the Vault that this represents,
108        /// as the link or resource that we fetch in this way may be manipulated,
109        /// so to find the address that a cut goes to you must get this struct and then
110        /// call receiver.borrow()!.owner.address on it.
111        /// This can be done efficiently in a script.
112        access(all) let receiver: Capability<&{FungibleToken.Receiver}>
113
114        /// The amount of the payment FungibleToken that will be paid to the receiver.
115        access(all) let amount: UFix64
116
117        /// initializer
118        ///
119        init(receiver: Capability<&{FungibleToken.Receiver}>, amount: UFix64) {
120            self.receiver = receiver
121            self.amount = amount
122        }
123    }
124
125
126    /// ListingDetails
127    /// A struct containing a Listing's data.
128    ///
129    access(all) struct ListingDetails {
130        /// The Storefront that the Listing is stored in.
131        /// Note that this resource cannot be moved to a different Storefront,
132        /// so this is OK. If we ever make it so that it *can* be moved,
133        /// this should be revisited.
134        access(all) var storefrontID: UInt64
135        /// Whether this listing has been purchased or not.
136        access(all) var purchased: Bool
137        /// The Type of the NonFungibleToken.NFT that is being listed.
138        access(all) let nftType: Type
139        /// The Resource ID of the NFT which can only be set in the contract
140        access(all) let nftUUID: UInt64
141        /// The unique identifier of the NFT that will get sell.
142        access(all) let nftID: UInt64
143        /// The Type of the FungibleToken that payments must be made in.
144        access(all) let salePaymentVaultType: Type
145        /// The amount that must be paid in the specified FungibleToken.
146        access(all) let salePrice: UFix64
147        /// This specifies the division of payment between recipients.
148        access(all) let saleCuts: [SaleCut]
149        /// Allow different dapp teams to provide custom strings as the distinguished string
150        /// that would help them to filter events related to their customID.
151        access(all) var customID: String?
152        /// Commission available to be claimed by whoever facilitates the sale.
153        access(all) let commissionAmount: UFix64
154        /// Expiry of listing
155        access(all) let expiry: UInt64
156
157        /// Irreversibly set this listing as purchased.
158        ///
159        access(contract) fun setToPurchased() {
160            self.purchased = true
161        }
162
163        access(contract) fun setCustomID(customID: String?){
164            self.customID = customID
165        }
166
167        /// Initializer
168        ///
169        init (
170            nftType: Type,
171            nftUUID: UInt64,
172            nftID: UInt64,
173            salePaymentVaultType: Type,
174            saleCuts: [SaleCut],
175            storefrontID: UInt64,
176            customID: String?,
177            commissionAmount: UFix64,
178            expiry: UInt64
179        ) {
180
181            pre {
182                // Validate the expiry
183                expiry > UInt64(getCurrentBlock().timestamp): "Expiry should be in the future"
184                // Validate the length of the sale cut
185                saleCuts.length > 0: "Listing must have at least one payment cut recipient"
186            }
187
188            self.storefrontID = storefrontID
189            self.purchased = false
190            self.nftType = nftType
191            self.nftUUID = nftUUID
192            self.nftID = nftID
193            self.salePaymentVaultType = salePaymentVaultType
194            self.customID = customID
195            self.commissionAmount = commissionAmount
196            self.expiry = expiry
197            self.saleCuts = saleCuts
198
199            // Calculate the total price from the cuts
200            var salePrice = commissionAmount
201            // Perform initial check on capabilities, and calculate sale price from cut amounts.
202            for cut in self.saleCuts {
203                // Make sure we can borrow the receiver.
204                // We will check this again when the token is sold.
205                cut.receiver.borrow()
206                    ?? panic("Cannot borrow receiver")
207                // Add the cut amount to the total price
208                salePrice = salePrice + cut.amount
209            }
210            assert(salePrice > 0.0, message: "Listing must have non-zero price")
211
212            // Store the calculated sale price
213            self.salePrice = salePrice
214        }
215    }
216
217
218    /// ListingPublic
219    /// An interface providing a useful public interface to a Listing.
220    ///
221    access(all) resource interface ListingPublic {
222        /// borrowNFT
223        /// This will assert in the same way as the NFT standard borrowNFT()
224        /// if the NFT is absent, for example if it has been sold via another listing.
225        ///
226        access(all) fun borrowNFT(): &{NonFungibleToken.NFT}?
227
228        /// purchase
229        /// Purchase the listing, buying the token.
230        /// This pays the beneficiaries and returns the token to the buyer.
231        ///
232        access(all) fun purchase(
233            payment: @{FungibleToken.Vault}, 
234            commissionRecipient: Capability<&{FungibleToken.Receiver}>?,
235        ): @{NonFungibleToken.NFT}
236
237        /// getDetails
238        /// Fetches the details of the listing.
239        access(all) view fun getDetails(): ListingDetails
240
241        /// getAllowedCommissionReceivers
242        /// Fetches the allowed marketplaces capabilities or commission receivers.
243        /// If it returns `nil` then commission is up to grab by anyone.
244        access(all) view fun getAllowedCommissionReceivers(): [Capability<&{FungibleToken.Receiver}>]?
245
246        /// hasListingBecomeGhosted
247        /// Tells whether listed NFT is present in provided capability.
248        /// If it returns `false` then it means listing becomes ghost or sold out.
249        access(all) view fun hasListingBecomeGhosted(): Bool
250
251    }
252
253
254    /// Listing
255    /// A resource that allows an NFT to be sold for an amount of a given FungibleToken,
256    /// and for the proceeds of that sale to be split between several recipients.
257    /// 
258    access(all) resource Listing: ListingPublic, Burner.Burnable {
259        /// The simple (non-Capability, non-complex) details of the sale
260        access(self) let details: ListingDetails
261
262        /// A capability allowing this resource to withdraw the NFT with the given ID from its collection.
263        /// This capability allows the resource to withdraw *any* NFT, so you should be careful when giving
264        /// such a capability to a resource and always check its code to make sure it will use it in the
265        /// way that it claims.
266        access(contract) let nftProviderCapability: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>
267
268        /// An optional list of marketplaces capabilities that are approved 
269        /// to receive the marketplace commission.
270        access(contract) let marketplacesCapability: [Capability<&{FungibleToken.Receiver}>]?
271
272        access(contract) fun burnCallback() {
273            // If the listing has not been purchased, we regard it as completed here.
274            // Otherwise we regard it as completed in purchase().
275            // This is because we destroy the listing in Storefront.removeListing()
276            // or Storefront.cleanup() .
277            // If we change this destructor, revisit those functions.
278            if !self.details.purchased {
279                emit ListingCompleted(
280                    listingResourceID: self.uuid,
281                    storefrontResourceID: self.details.storefrontID,
282                    purchased: self.details.purchased,
283                    nftType: self.details.nftType,
284                    nftUUID: self.details.nftUUID,
285                    nftID: self.details.nftID,
286                    salePaymentVaultType: self.details.salePaymentVaultType,
287                    salePrice: self.details.salePrice,
288                    customID: self.details.customID,
289                    commissionAmount: self.details.commissionAmount,
290                    commissionReceiver: nil,
291                    expiry: self.details.expiry
292                )
293            }
294        }
295
296        /// borrowNFT
297        /// Return the reference of the NFT that is listed for sale.
298        /// if the NFT is absent, for example if it has been sold via another listing.
299        /// it will return nil.
300        ///
301        access(all) fun borrowNFT(): &{NonFungibleToken.NFT}? {
302            if let ref = self.nftProviderCapability.borrow()!.borrowNFT(self.details.nftID) {
303                if ref.isInstance(self.details.nftType) && ref.id == self.details.nftID {
304                    return ref
305                }
306            }
307
308            return nil
309        }
310
311        /// getDetails
312        /// Get the details of listing.
313        ///
314        access(all) view fun getDetails(): ListingDetails {
315            return self.details
316        }
317
318        /// getAllowedCommissionReceivers
319        /// Fetches the allowed marketplaces capabilities or commission receivers.
320        /// If it returns `nil` then commission is up to grab by anyone.
321        access(all) view fun getAllowedCommissionReceivers(): [Capability<&{FungibleToken.Receiver}>]? {
322            return self.marketplacesCapability
323        }
324
325        /// hasListingBecomeGhosted
326        /// Tells whether listed NFT is present in provided capability.
327        /// If it returns `false` then it means listing becomes ghost or sold out.
328        access(all) view fun hasListingBecomeGhosted(): Bool {
329            if let providerRef = self.nftProviderCapability.borrow() {
330                return providerRef.borrowNFT(self.details.nftID) != nil
331            }
332            return false
333        }
334
335        /// purchase
336        /// Purchase the listing, buying the token.
337        /// This pays the beneficiaries and commission to the facilitator and returns extra token to the buyer.
338        /// This also cleans up duplicate listings for the item being purchased.
339        access(all) fun purchase(
340            payment: @{FungibleToken.Vault}, 
341            commissionRecipient: Capability<&{FungibleToken.Receiver}>?,
342        ): @{NonFungibleToken.NFT} {
343
344            pre {
345                self.details.purchased == false: "listing has already been purchased"
346                payment.isInstance(self.details.salePaymentVaultType): "payment vault is not requested fungible token"
347                payment.balance == self.details.salePrice: "payment vault does not contain requested price"
348                self.details.expiry > UInt64(getCurrentBlock().timestamp): "Listing is expired"
349                self.owner != nil : "Resource doesn't have the assigned owner"
350            }
351            
352            // Make sure the listing cannot be purchased again.
353            self.details.setToPurchased() 
354            
355            if self.details.commissionAmount > 0.0 {
356                // If commission recipient is nil, Throw panic.
357                let commissionReceiver = commissionRecipient ?? panic("Commission recipient can't be nil")
358                if self.marketplacesCapability != nil {
359                    var isCommissionRecipientHasValidType = false
360                    var isCommissionRecipientAuthorised = false
361                    for cap in self.marketplacesCapability! {
362                        // Check 1: Should have the same type
363                        if cap.getType() == commissionReceiver.getType() {
364                            isCommissionRecipientHasValidType = true
365                            // Check 2: Should have the valid market address that holds approved capability.
366                            if cap.address == commissionReceiver.address && cap.check() {
367                                isCommissionRecipientAuthorised = true
368                                break
369                            }
370                        }
371                    }
372                    assert(isCommissionRecipientHasValidType, message: "Given recipient does not has valid type")
373                    assert(isCommissionRecipientAuthorised, message: "Given recipient is not authorised to receive the commission")
374                }
375                let commissionPayment <- payment.withdraw(amount: self.details.commissionAmount)
376                let recipient = commissionReceiver.borrow() ?? panic("Unable to borrow the recipient capability")
377                recipient.deposit(from: <- commissionPayment)
378            }
379            // Fetch the token to return to the purchaser.
380            let nft <-self.nftProviderCapability.borrow()!.withdraw(withdrawID: self.details.nftID)
381            // Neither receivers nor providers are trustworthy, they must implement the correct
382            // interface but beyond complying with its pre/post conditions they are not guaranteed
383            // to implement the functionality behind the interface in any given way.
384            // Therefore we cannot trust the Collection resource behind the interface,
385            // and we must check the NFT resource it gives us to make sure that it is the correct one.
386            assert(nft.getType() == self.details.nftType, message: "withdrawn NFT is not of specified type")
387            assert(nft.id == self.details.nftID, message: "withdrawn NFT does not have specified ID")
388
389            // Fetch the duplicate listing for the given NFT
390            // Access the StoreFrontManager resource reference to remove the duplicate listings if purchase would happen successfully.
391            let storeFrontPublicRef = getAccount(self.owner!.address).capabilities.borrow<&{NFTStorefrontV2.StorefrontPublic}>(
392                    NFTStorefrontV2.StorefrontPublicPath
393                ) ?? panic("Unable to borrow the storeFrontManager resource")
394            let duplicateListings = storeFrontPublicRef.getDuplicateListingIDs(
395                    nftType: self.details.nftType,
396                    nftID: self.details.nftID,
397                    listingID: self.uuid
398                )
399
400            // Let's force removal of the listing in this storefront for the NFT that is being purchased. 
401            for listingID in duplicateListings {
402                storeFrontPublicRef.cleanup(listingResourceID: listingID)
403            }
404
405            // Rather than aborting the transaction if any receiver is absent when we try to pay it,
406            // we send the cut to the first valid receiver.
407            // The first receiver should therefore either be the seller, or an agreed recipient for
408            // any unpaid cuts.
409            var residualReceiver: &{FungibleToken.Receiver}? = nil
410            // Pay the commission 
411            // Pay each beneficiary their amount of the payment.
412
413            for cut in self.details.saleCuts {
414                if let receiver = cut.receiver.borrow() {
415                   let paymentCut <- payment.withdraw(amount: cut.amount)
416                    receiver.deposit(from: <-paymentCut)
417                    if (residualReceiver == nil) {
418                        residualReceiver = receiver
419                    }
420                } else {
421                    emit UnpaidReceiver(receiver: cut.receiver.address, entitledSaleCut: cut.amount)
422                }
423            }
424
425            assert(residualReceiver != nil, message: "No valid payment receivers")
426
427            // At this point, if all receivers were active and available, then the payment Vault will have
428            // zero tokens left, and this will functionally be a no-op that consumes the empty vault
429            residualReceiver!.deposit(from: <-payment)
430
431            // If the listing is purchased, we regard it as completed here.
432            // Otherwise we regard it as completed in the destructor.
433
434            var commissionReceiver: Address?  = nil
435            if (self.details.commissionAmount != 0.0) {
436                commissionReceiver = commissionRecipient!.address
437            }
438
439            emit ListingCompleted(
440                listingResourceID: self.uuid,
441                storefrontResourceID: self.details.storefrontID,
442                purchased: self.details.purchased,
443                nftType: self.details.nftType,
444                nftUUID: self.details.nftUUID,
445                nftID: self.details.nftID,
446                salePaymentVaultType: self.details.salePaymentVaultType,
447                salePrice: self.details.salePrice,
448                customID: self.details.customID,
449                commissionAmount: self.details.commissionAmount,
450                commissionReceiver: commissionReceiver,
451                expiry: self.details.expiry
452            )
453
454            return <-nft
455        }
456
457        // destructor event
458        //
459        access(all) event ResourceDestroyed(
460            listingResourceID: UInt64 = self.uuid,
461            storefrontResourceID: UInt64 = self.details.storefrontID,
462            purchased: Bool = self.details.purchased,
463            nftType: String = self.details.nftType.identifier,
464            nftUUID: UInt64 = self.details.nftUUID,
465            nftID: UInt64 = self.details.nftID,
466            salePaymentVaultType: String = self.details.salePaymentVaultType.identifier,
467            salePrice: UFix64 = self.details.salePrice,
468            customID: String? = self.details.customID,
469            commissionAmount: UFix64 = self.details.commissionAmount,
470            commissionReceiver: Address? = nil,
471            expiry: UInt64 = self.details.expiry
472        )
473
474        /// initializer
475        ///
476        init (
477            nftProviderCapability: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}>,
478            nftType: Type,
479            nftUUID: UInt64,
480            nftID: UInt64,
481            salePaymentVaultType: Type,
482            saleCuts: [SaleCut],
483            marketplacesCapability: [Capability<&{FungibleToken.Receiver}>]?,
484            storefrontID: UInt64,
485            customID: String?,
486            commissionAmount: UFix64,
487            expiry: UInt64
488        ) {
489            // Store the sale information
490            self.details = ListingDetails(
491                nftType: nftType,
492                nftUUID: nftUUID,
493                nftID: nftID,
494                salePaymentVaultType: salePaymentVaultType,
495                saleCuts: saleCuts,
496                storefrontID: storefrontID,
497                customID: customID,
498                commissionAmount: commissionAmount,
499                expiry: expiry
500            )
501
502            // Store the NFT provider
503            self.nftProviderCapability = nftProviderCapability
504            self.marketplacesCapability = marketplacesCapability
505
506            // Check that the provider contains the NFT.
507            // We will check it again when the token is sold.
508            // We cannot move this into a function because initializers cannot call member functions.
509            let provider = self.nftProviderCapability.borrow()
510            assert(provider != nil, message: "cannot borrow nftProviderCapability")
511
512            // This will precondition assert if the token is not available.
513            let nft = provider!.borrowNFT(self.details.nftID)
514            assert(nft!.getType() == self.details.nftType, message: "token is not of specified type")
515            assert(nft?.id == self.details.nftID, message: "token does not have specified ID")
516        }
517    }
518
519    /// StorefrontManager
520    /// An interface for adding and removing Listings within a Storefront,
521    /// intended for use by the Storefront's owner
522    ///
523    access(all) resource interface StorefrontManager {
524        /// createListing
525        /// Allows the Storefront owner to create and insert Listings.
526        ///
527        access(CreateListing) fun createListing(
528            nftProviderCapability: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}>,
529            nftType: Type,
530            nftID: UInt64,
531            salePaymentVaultType: Type,
532            saleCuts: [SaleCut],
533            marketplacesCapability: [Capability<&{FungibleToken.Receiver}>]?,
534            customID: String?,
535            commissionAmount: UFix64,
536            expiry: UInt64
537        ): UInt64
538
539        /// removeListing
540        /// Allows the Storefront owner to remove any sale listing, accepted or not.
541        ///
542        access(RemoveListing) fun removeListing(listingResourceID: UInt64)
543    }
544
545    /// StorefrontPublic
546    /// An interface to allow listing and borrowing Listings, and purchasing items via Listings
547    /// in a Storefront.
548    ///
549    access(all) resource interface StorefrontPublic {
550        access(all) view fun getListingIDs(): [UInt64]
551        access(all) fun getDuplicateListingIDs(nftType: Type, nftID: UInt64, listingID: UInt64): [UInt64]
552        access(all) view fun borrowListing(listingResourceID: UInt64): &{ListingPublic}? {
553            post {
554                result == nil || result!.getType() == Type<@Listing>():
555                    "Cannot borrow a non-NFTStorefrontV2.Listing!"
556            }
557        }
558        access(all) fun cleanupExpiredListings(fromIndex: UInt64, toIndex: UInt64)
559        access(contract) fun cleanup(listingResourceID: UInt64)
560        access(all) fun getExistingListingIDs(nftType: Type, nftID: UInt64): [UInt64]
561        access(all) fun cleanupPurchasedListings(listingResourceID: UInt64)
562        access(all) fun cleanupGhostListings(listingResourceID: UInt64)
563   }
564
565    /// Storefront
566    /// A resource that allows its owner to manage a list of Listings, and anyone to interact with them
567    /// in order to query their details and purchase the NFTs that they represent.
568    ///
569    access(all) resource Storefront : StorefrontManager, StorefrontPublic {
570        // Resource destroyed event
571        access(all) event ResourceDestroyed(
572            storefrontResourceID: UInt64 = self.uuid
573        )
574
575        /// The dictionary of Listing uuids to Listing resources.
576        access(contract) var listings: @{UInt64: Listing}
577        /// Dictionary to keep track of listing ids for same NFTs listing.
578        /// nftType.identifier -> nftID -> [listing resource ID]
579        access(contract) var listedNFTs: {String: {UInt64 : [UInt64]}}
580
581        /// insert
582        /// Create and publish a Listing for an NFT.
583        ///
584         access(CreateListing) fun createListing(
585            nftProviderCapability: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}>,
586            nftType: Type,
587            nftID: UInt64,
588            salePaymentVaultType: Type,
589            saleCuts: [SaleCut],
590            marketplacesCapability: [Capability<&{FungibleToken.Receiver}>]?,
591            customID: String?,
592            commissionAmount: UFix64,
593            expiry: UInt64
594         ): UInt64 {
595            
596            // let's ensure that the seller does indeed hold the NFT being listed
597            let collectionRef = nftProviderCapability.borrow()
598                ?? panic("Could not borrow reference to collection")
599            let nftRef = collectionRef.borrowNFT(nftID)
600                ?? panic("Could not borrow a reference to the desired NFT ID")
601
602            // Instead of letting an arbitrary value be set for the UUID of a given NFT, the contract
603            // should fetch it itself     
604            let uuid = nftRef.uuid
605            let listing <- create Listing(
606                nftProviderCapability: nftProviderCapability,
607                nftType: nftType,
608                nftUUID: uuid,
609                nftID: nftID,
610                salePaymentVaultType: salePaymentVaultType,
611                saleCuts: saleCuts,
612                marketplacesCapability: marketplacesCapability,
613                storefrontID: self.uuid,
614                customID: customID,
615                commissionAmount: commissionAmount,
616                expiry: expiry
617            )
618        
619            let listingResourceID = listing.uuid
620            let listingPrice = listing.getDetails().salePrice
621            // Add the new listing to the dictionary.
622            let oldListing <- self.listings[listingResourceID] <- listing
623            // Note that oldListing will always be nil, but we have to handle it.
624
625            Burner.burn(<-oldListing)
626
627            // Add the `listingResourceID` in the tracked listings.
628            self.addDuplicateListing(nftIdentifier: nftType.identifier, nftID: nftID, listingResourceID: listingResourceID)
629
630            // Scraping addresses from the capabilities to emit in the event.
631            var allowedCommissionReceivers : [Address]? = nil
632            if let allowedReceivers = marketplacesCapability {
633                // Small hack here to make `allowedCommissionReceivers` variable compatible to
634                // array properties.
635                allowedCommissionReceivers = []
636                for receiver in allowedReceivers {
637                    allowedCommissionReceivers!.append(receiver.address)
638                }
639            }
640
641            emit ListingAvailable(
642                storefrontAddress: self.owner?.address!,
643                listingResourceID: listingResourceID,
644                nftType: nftType,
645                nftUUID: uuid,
646                nftID: nftID,
647                salePaymentVaultType: salePaymentVaultType,
648                salePrice: listingPrice,
649                customID: customID,
650                commissionAmount: commissionAmount,
651                commissionReceivers: allowedCommissionReceivers,
652                expiry: expiry
653            )
654
655            return listingResourceID
656        }
657
658        /// addDuplicateListing
659        /// Helper function that allows to add duplicate listing of given nft in a map.
660        /// 
661        access(contract) fun addDuplicateListing(nftIdentifier: String, nftID: UInt64, listingResourceID: UInt64) {
662             if !self.listedNFTs.containsKey(nftIdentifier) {
663                self.listedNFTs.insert(key: nftIdentifier, {nftID: [listingResourceID]})
664            } else {
665                if !self.listedNFTs[nftIdentifier]!.containsKey(nftID) {
666                    self.listedNFTs[nftIdentifier]!.insert(key: nftID, [listingResourceID])
667                } else {
668                    self.listedNFTs[nftIdentifier]![nftID]!.append(listingResourceID)
669                } 
670            }
671        }
672
673        /// removeDuplicateListing
674        /// Helper function that allows to remove duplicate listing of given nft from a map.
675        /// 
676        access(contract) fun removeDuplicateListing(nftIdentifier: String, nftID: UInt64, listingResourceID: UInt64) {
677            // Remove the listing from the listedNFTs dictionary.
678            let listingIndex = self.listedNFTs[nftIdentifier]![nftID]!.firstIndex(of: listingResourceID) ?? panic("Should contain the index")
679            self.listedNFTs[nftIdentifier]![nftID]!.remove(at: listingIndex)
680        }
681        
682        /// removeListing
683        /// Remove a Listing that has not yet been purchased from the collection and destroy it.
684        /// It can only be executed by the StorefrontManager resource owner.
685        ///
686        access(RemoveListing) fun removeListing(listingResourceID: UInt64) {
687            let listing <- self.listings.remove(key: listingResourceID)
688                ?? panic("missing Listing")
689            let listingDetails = listing.getDetails()
690            self.removeDuplicateListing(nftIdentifier: listingDetails.nftType.identifier, nftID: listingDetails.nftID, listingResourceID: listingResourceID)
691            // This will emit a ListingCompleted event.
692            Burner.burn(<-listing)
693        }
694
695        /// getListingIDs
696        /// Returns an array of the Listing resource IDs that are in the collection
697        ///
698        access(all) view fun getListingIDs(): [UInt64] {
699            return self.listings.keys
700        }
701
702        /// getExistingListingIDs
703        /// Returns an array of listing IDs of the given `nftType` and `nftID`.
704        ///
705        access(all) fun getExistingListingIDs(nftType: Type, nftID: UInt64): [UInt64] {
706            if self.listedNFTs[nftType.identifier] == nil || self.listedNFTs[nftType.identifier]![nftID] == nil {
707                return []
708            }
709            var listingIDs = self.listedNFTs[nftType.identifier]![nftID]!
710            return listingIDs
711        }
712
713        /// cleanupPurchasedListings
714        /// Allows anyone to remove already purchased listings.
715        ///
716        access(all) fun cleanupPurchasedListings(listingResourceID: UInt64) {
717            pre {
718                self.listings[listingResourceID] != nil: "could not find listing with given id"
719                self.borrowListing(listingResourceID: listingResourceID)!.getDetails().purchased == true: "listing not purchased yet"
720            }
721            let listing <- self.listings.remove(key: listingResourceID)!
722            let listingDetails = listing.getDetails()
723            self.removeDuplicateListing(nftIdentifier: listingDetails.nftType.identifier, nftID: listingDetails.nftID, listingResourceID: listingResourceID)
724
725            Burner.burn(<-listing)
726        }
727
728        /// getDuplicateListingIDs
729        /// Returns an array of listing IDs that are duplicates of the given `nftType` and `nftID`.
730        ///
731        access(all) fun getDuplicateListingIDs(nftType: Type, nftID: UInt64, listingID: UInt64): [UInt64] {
732            var listingIDs = self.getExistingListingIDs(nftType: nftType, nftID: nftID)
733
734            // Verify that given listing Id also a part of the `listingIds`
735            let doesListingExist = listingIDs.contains(listingID)
736            // Find out the index of the existing listing.
737            if doesListingExist {
738                var index: Int = 0
739                for id in listingIDs {
740                    if id == listingID {
741                        break
742                    }
743                    index = index + 1
744                }
745                listingIDs.remove(at:index)
746                return listingIDs
747            } 
748           return []
749        }
750
751        /// cleanupExpiredListings
752        /// Cleanup the expired listing by iterating over the provided range of indexes.
753        ///
754        access(all) fun cleanupExpiredListings(fromIndex: UInt64, toIndex: UInt64) {
755            pre {
756                fromIndex <= toIndex : "Incorrect start index"
757                Int(toIndex - fromIndex) < self.getListingIDs().length : "Provided range is out of bound"
758            }
759            var index = fromIndex
760            let listingsIDs = self.getListingIDs()
761            while index <= toIndex {
762                // There is a possibility that some index may not have the listing.
763                // because of that instead of failing the transaction, Execution moved to next index or listing.
764                
765                if let listing = self.borrowListing(listingResourceID: listingsIDs[index]) {
766                    if listing.getDetails().expiry <= UInt64(getCurrentBlock().timestamp) {
767                        self.cleanup(listingResourceID: listingsIDs[index])
768                    }
769                }
770                index = index + UInt64(1) 
771            }
772        } 
773
774        /// borrowSaleItem
775        /// Returns a read-only view of the SaleItem for the given listingID if it is contained by this collection.
776        ///
777        access(all) view fun borrowListing(listingResourceID: UInt64): &{ListingPublic}? {
778            return &self.listings[listingResourceID]
779        }
780
781        /// cleanup
782        /// Remove an listing, When given listing is duplicate or expired
783        /// Only contract is allowed to execute it.
784        ///
785        access(contract) fun cleanup(listingResourceID: UInt64) {
786            pre {
787                self.listings[listingResourceID] != nil: "Could not find listing with given id"
788            }
789            let listing <- self.listings.remove(key: listingResourceID)!
790            let listingDetails = listing.getDetails()
791            self.removeDuplicateListing(nftIdentifier: listingDetails.nftType.identifier, nftID: listingDetails.nftID, listingResourceID: listingResourceID)
792
793            Burner.burn(<-listing)
794        }
795
796        /// cleanupGhostListings
797        /// Allow anyone to cleanup ghost listings
798        /// Listings will become ghost listings if stored provider capability doesn't hold
799        /// the NFT anymore.
800        ///
801        /// @param listingResourceID ID of the listing resource which would get removed if it become ghost listing.
802        access(all) fun cleanupGhostListings(listingResourceID: UInt64) {
803            pre {
804                self.listings[listingResourceID] != nil: "Could not find listing with given id"
805            }
806            let listingRef = self.borrowListing(listingResourceID: listingResourceID)!
807            let details = listingRef.getDetails()
808            assert(!details.purchased, message: "Given listing is already purchased")
809            assert(!listingRef.hasListingBecomeGhosted(), message: "Listing is not ghost listing")
810            let listing <- self.listings.remove(key: listingResourceID)!
811            let duplicateListings = self.getDuplicateListingIDs(nftType: details.nftType, nftID: details.nftID, listingID: listingResourceID)
812
813            // Let's force removal of the listing in this storefront for the NFT that is being ghosted. 
814            for listingID in duplicateListings {
815                self.cleanup(listingResourceID: listingID)
816            }
817            Burner.burn(<-listing)
818        }
819
820        /// constructor
821        ///
822        init () {
823            self.listings <- {}
824            self.listedNFTs = {}
825
826            // Let event consumers know that this storefront exists
827            emit StorefrontInitialized(storefrontResourceID: self.uuid)
828        }
829    }
830
831    /// createStorefront
832    /// Make creating a Storefront publicly accessible.
833    ///
834    access(all) fun createStorefront(): @Storefront {
835        return <-create Storefront()
836    }
837
838    init () {
839        self.StorefrontStoragePath = /storage/NFTStorefrontV2
840        self.StorefrontPublicPath = /public/NFTStorefrontV2
841    }
842}
843