Smart Contract

NFTStorefrontX2

A.66b60643244a7738.NFTStorefrontX2

Deployed

14h ago
Feb 28, 2026, 02:29:07 AM UTC

Dependents

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