Smart Contract

PuffPalzMainStorefront

A.a3eb9784ae7dc9c8.PuffPalzMainStorefront

Deployed

14h ago
Feb 28, 2026, 02:28:25 AM UTC

Dependents

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