Smart Contract

MainStorefrontX1

A.66b60643244a7738.MainStorefrontX1

Deployed

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

Dependents

0 imports
1// Mainnet
2import NonFungibleToken from 0x1d7e57aa55817448
3import FungibleToken from 0xf233dcee88fe0abe
4
5access(all) contract MainStorefrontX1 {
6    
7    access(all) event MainStorefrontInitialized()
8    access(all) event StorefrontInitialized(storefrontResourceID: UInt64)
9    access(all) event StorefrontDestroyed(storefrontResourceID: UInt64)
10    
11    access(all) event ListingAvailable(
12        storefrontAddress: Address,
13        listingResourceID: UInt64,
14        nftType: Type,
15        nftIDs: [UInt64],
16        ftVaultType: Type,
17        price: UFix64
18    )
19
20    access(all) event ListingCompleted(
21        listingResourceID: UInt64, 
22        storefrontResourceID: UInt64, 
23        purchased: Bool,
24        nftType: Type,
25        nftIDs: [UInt64]
26    )
27
28    access(all) let StorefrontStoragePath: StoragePath
29    access(all) let StorefrontPublicPath: PublicPath
30
31    access(all) struct ListingDetails {
32
33        access(all) var storefrontID: UInt64
34        // Whether this listing has been purchased or not.
35        access(all) var purchased: Bool
36        // The Type of the NonFungibleToken.NFT that is being listed.
37        access(all) let nftType: Type
38        // The ID of the NFT within that type.
39        access(all) let nftIDs: [UInt64]
40        // The Type of the FungibleToken that payments must be made in.
41        access(all) let salePaymentVaultType: Type
42        // The amount that must be paid in the specified FungibleToken.
43        access(all) let salePrice: UFix64
44
45        access(all) let receiver: Capability<&{FungibleToken.Receiver}>
46
47        access(all) let discount: UFix64?
48
49        // setToPurchased
50        // Irreversibly set this listing as purchased.
51        //
52        access(contract) fun setToPurchased() {
53            self.purchased = true
54        }
55
56        // initializer
57        //
58        init (
59            nftType: Type,
60            nftIDs: [UInt64],
61            salePaymentVaultType: Type,
62            storefrontID: UInt64,
63            salePrice: UFix64,
64            receiver: Capability<&{FungibleToken.Receiver}>,
65            discount: UFix64?
66        ) {
67            self.storefrontID = storefrontID
68            self.purchased = false
69            self.nftType = nftType
70            self.nftIDs = nftIDs
71            self.salePaymentVaultType = salePaymentVaultType
72            self.salePrice = salePrice
73            self.receiver = receiver
74            //Store the discounts
75            self.discount = discount
76        }
77    }
78
79        // ListingPublic
80    // An interface providing a useful access(all)lic interface to a Listing.
81    //
82    access(all) resource interface ListingPublic {
83        // borrowNFT
84        // This will assert in the same way as the NFT standard borrowNFT()
85        // if the NFT is absent, for example if it has been sold via another listing.
86        //
87        // access(all) fun borrowNFT(): &NonFungibleToken.NFT
88
89        // purchase
90        // Purchase the listing, buying the token.
91        // This pays the beneficiaries and returns the token to the buyer.
92        //
93        access(all) fun purchase(payment: @{FungibleToken.Vault}, collection: &{NonFungibleToken.CollectionPublic}): @{NonFungibleToken.NFT}?
94
95        // getDetails
96        //
97        access(all) fun getDetails(): ListingDetails
98
99        }
100
101            // Listing
102    // A resource that allows an NFT to be sold for an amount of a given FungibleToken,
103    // and for the proceeds of that sale to be split between several recipients.
104    access(all) resource Listing: ListingPublic {
105        // The simple (non-Capability, non-complex) details of the sale
106        access(self) let details: ListingDetails
107
108        /// A capability allowing this resource to withdraw the NFT with the given ID from its collection.
109        /// This capability allows the resource to withdraw *any* NFT, so you should be careful when giving
110        /// such a capability to a resource and always check its code to make sure it will use it in the
111        /// way that it claims.
112        access(contract) let nftProviderCapability: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>
113
114        // borrowNFT
115        // This will assert in the same way as the NFT standard borrowNFT()
116        // if the NFT is absent, for example if it has been sold via another listing.
117        /*
118        access(all) fun borrowNFT(): &NonFungibleToken.NFT {
119                let ref = self.nftProviderCapability.borrow()!.borrowNFT(id: self.getDetails().nftIDs)
120                //- CANNOT DO THIS IN PRECONDITION: "member of restricted type is not accessible: isInstance"
121                //  result.isInstance(self.getDetails().nftType): "token has wrong type"
122                assert(ref.isInstance(self.getDetails().nftType), message: "token has wrong type")
123                assert(ref.id == self.getDetails().nftIDs, message: "token has wrong ID")
124                return (ref as &NonFungibleToken.NFT?)!
125        }*/
126
127
128           // getDetails
129        // Get the details of the current state of the Listing as a struct.
130        access(all) fun getDetails(): ListingDetails {
131            return self.details
132        }
133        
134        // Purchase the listing, buying the token.
135        //The purchase function receives payment and capability from the collection to which the NFT is to be sent. 
136        //This checks the user's address and determines whether they are entitled to a discount
137        access(all) fun purchase(
138            payment: @{FungibleToken.Vault}, 
139            collection: &{NonFungibleToken.CollectionPublic}
140        ): @{NonFungibleToken.NFT}? {
141            pre {
142                self.details.purchased == false: "This listing has already been purchased."
143                payment.balance >= self.details.salePrice: "Insufficient funds to purchase the listing."
144            }
145
146            var nfts: @{NonFungibleToken.NFT}? <- nil
147
148            for nftID in self.details.nftIDs {
149                let nft <- self.nftProviderCapability.borrow()!.withdraw(withdrawID: nftID)
150                assert(nft.isInstance(self.details.nftType), message: "Withdrawn NFT is not of the expected type.")
151                collection.deposit(token: <- nft)
152            }
153
154            self.details.receiver.borrow()!.deposit(from: <- payment)
155            self.details.setToPurchased()
156
157            emit ListingCompleted(
158                listingResourceID: self.uuid,
159                storefrontResourceID: self.details.storefrontID,
160                purchased: true,
161                nftType: self.details.nftType,
162                nftIDs: self.details.nftIDs // Emitting all NFT IDs involved in the listing
163            )
164
165            return <- nfts // Return the collected NFTs (if needed)
166        }
167
168
169        // initializer
170        init (
171            nftProviderCapability: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}>,
172            nftType: Type,
173            nftIDs: [UInt64],
174            salePaymentVaultType: Type,
175            salePrice: UFix64,
176            storefrontID: UInt64,
177            receiver: Capability<&{FungibleToken.Receiver}>,
178            discount: UFix64?
179        ) {
180            // Store the sale information
181            self.details = ListingDetails(
182                nftType: nftType,
183                nftIDs: nftIDs,
184                salePaymentVaultType: salePaymentVaultType,
185                storefrontID: storefrontID,
186                salePrice: salePrice,
187                receiver: receiver,
188                discount: discount
189            )
190
191            // Store the NFT provider
192            self.nftProviderCapability = nftProviderCapability
193
194            // Check that the provider contains the NFT.
195            // We will check it again when the token is sold.
196            // We cannot move this into a function because initializers cannot call member functions.
197            let provider = self.nftProviderCapability.borrow()
198            assert(provider != nil, message: "cannot borrow nftProviderCapability")
199
200            // This will precondition assert if the token is not available.
201/*
202            let nft = provider!.borrowNFT(id: self.details.nftIDs)
203            assert(nft.isInstance(self.details.nftType), message: "token is not of specified type")*/
204
205        }
206    }
207
208        // StorefrontManager
209    // An interface for adding and removing Listings within a Storefront,
210    // intended for use by the Storefront's own
211    //
212    access(all) resource interface StorefrontManager {
213        // createListing
214        // Allows the Storefront owner to create and insert Listings.
215        //
216        access(all) fun createListing(
217            nftProviderCapability: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}>,
218            nftType: Type,
219            nftIDs: [UInt64],
220            receiver: Capability<&{FungibleToken.Receiver}>,
221            salePaymentVaultType: Type,
222            salePrice: UFix64,
223            discount: UFix64?
224        ): UInt64
225        // removeListing
226        // Allows the Storefront owner to remove any sale listing, acepted or not.
227        //
228        access(all) fun removeListing(listingResourceID: UInt64)
229    }
230
231    // StorefrontPublic
232    // An interface to allow listing and borrowing Listings, and purchasing items via Listings
233    // in a Storefront.
234    //
235    access(all) resource interface StorefrontPublic {
236        access(all) fun getListingIDs(): [UInt64]
237        access(all) fun borrowListing(listingResourceID: UInt64): &{ListingPublic}?
238        access(all) fun cleanup(listingResourceID: UInt64)
239   }
240
241    // Storefront
242    // A resource that allows its owner to manage a list of Listings, and anyone to interact with them
243    // in order to query their details and purchase the NFTs that they represent.
244    //
245    access(all) resource Storefront : StorefrontManager, StorefrontPublic {
246        // The dictionary of Listing uuids to Listing resources.
247        access(self) var listings: @{UInt64: Listing}
248
249        // insert
250        // Create and publish a Listing for an NFT.
251        //
252         access(all) fun createListing(
253            nftProviderCapability: Capability<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}>,
254            nftType: Type,
255            nftIDs: [UInt64],
256            receiver: Capability<&{FungibleToken.Receiver}>,
257            salePaymentVaultType: Type,
258            salePrice: UFix64,
259            discount: UFix64?
260         ): UInt64 {
261            let listing <- create Listing(
262                nftProviderCapability: nftProviderCapability,
263                nftType: nftType,
264                nftIDs: nftIDs,
265                salePaymentVaultType: salePaymentVaultType,
266                salePrice: salePrice,
267                storefrontID: self.uuid,
268                receiver: receiver,
269                discount: discount
270            )
271
272            let listingResourceID = listing.uuid
273            let listingPrice = listing.getDetails().salePrice
274
275            // Add the new listing to the dictionary.
276            let oldListing <- self.listings[listingResourceID] <- listing
277            // Note that oldListing will always be nil, but we have to handle it.
278
279            destroy oldListing
280
281            emit ListingAvailable(
282                storefrontAddress: self.owner?.address!,
283                listingResourceID: listingResourceID,
284                nftType: nftType,
285                nftIDs: nftIDs,
286                ftVaultType: salePaymentVaultType,
287                price: listingPrice
288            )
289
290            return listingResourceID
291        }
292        
293
294        // removeListing
295        // Remove a Listing that has not yet been purchased from the collection and destroy it.
296        //
297        access(all) fun removeListing(listingResourceID: UInt64) {
298            let listing <- self.listings.remove(key: listingResourceID)
299                ?? panic("missing Listing")
300    
301            // This will emit a ListingCompleted event.
302            destroy listing
303        }
304
305        // getListingIDs
306        // Returns an array of the Listing resource IDs that are in the collection
307        //
308        access(all) fun getListingIDs(): [UInt64] {
309            return self.listings.keys
310        }
311
312        // borrowSaleItem
313        // Returns a read-only view of the SaleItem for the given listingID if it is contained by this collection.
314        //
315        access(all) fun borrowListing(listingResourceID: UInt64): &{ListingPublic}? {
316            if self.listings[listingResourceID] != nil {
317                return (&self.listings[listingResourceID] as &{ListingPublic}?)
318            } else {
319                return nil
320            }
321        }
322
323        // cleanup
324        // Remove an listing *if* it has been purchased.
325        // Anyone can call, but at present it only benefits the account owner to do so.
326        // Kind purchasers can however call it if they like.
327        //
328        access(all) fun cleanup(listingResourceID: UInt64) {
329            pre {
330                self.listings[listingResourceID] != nil: "could not find listing with given id"
331            }
332
333            let listing <- self.listings.remove(key: listingResourceID)!
334            assert(listing.getDetails().purchased == true, message: "listing is not purchased, only admin can remove")
335            destroy listing
336        }
337
338
339        // constructor
340        //
341        init () {
342            self.listings <- {}
343
344            // Let event consumers know that this storefront exists
345            emit StorefrontInitialized(storefrontResourceID: self.uuid)
346        }
347    }
348
349    // createStorefront
350    // Make creating a Storefront publicly accessible.
351    //
352    access(all) fun createStorefront(): @Storefront {
353        return <-create Storefront()
354    }
355
356    init () {
357        self.StorefrontStoragePath = /storage/MainStorefrontX
358        self.StorefrontPublicPath = /public/MainStorefrontX
359
360        emit MainStorefrontInitialized()
361    }
362    
363
364
365}