Smart Contract

MainCCStorefront

A.f887ece39166906e.MainCCStorefront

Deployed

1d ago
Feb 26, 2026, 09:44:38 PM UTC

Dependents

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