Smart Contract

PortraitsStorefront

A.66b60643244a7738.PortraitsStorefront

Deployed

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

Dependents

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