Smart Contract

CarClubStorefront

A.f887ece39166906e.CarClubStorefront

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
13pub contract CarClubStorefront {
14    
15    pub event CarClubStorefrontInitialized()
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 StorefrontStoragePath: StoragePath
37    pub let StorefrontPublicPath: 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            }
146
147            for nftID in self.details.nftIDs {
148                let nft <- self.nftProviderCapability.borrow()!.withdraw(withdrawID: nftID)
149                assert(nft.isInstance(self.details.nftType), message: "Withdrawn NFT is not of the expected type.")
150                collection.deposit(token: <- nft)
151            }
152
153            self.details.receiver.borrow()!.deposit(from: <- payment)
154            self.details.setToPurchased()
155
156            emit ListingCompleted(
157                listingResourceID: self.uuid,
158                storefrontResourceID: self.details.storefrontID,
159                purchased: true,
160                nftType: self.details.nftType,
161                nftIDs: self.details.nftIDs // Emitting all NFT IDs involved in the listing
162            )
163        }
164
165        // destructor
166        destroy () {
167            if !self.details.purchased {
168
169                emit ListingCompleted(
170                    listingResourceID: self.uuid,
171                    storefrontResourceID: self.details.storefrontID,
172                    purchased: self.details.purchased,
173                    nftType: self.details.nftType,
174                    nftIDs: self.details.nftIDs
175                )
176            }
177        }
178
179        // initializer
180        init (
181            nftProviderCapability: Capability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>,
182            nftType: Type,
183            nftIDs: [UInt64],
184            salePaymentVaultType: Type,
185            salePrice: UFix64,
186            storefrontID: UInt64,
187            receiver: Capability<&{FungibleToken.Receiver}>,
188            discount: UFix64?
189        ) {
190            // Store the sale information
191            self.details = ListingDetails(
192                nftType: nftType,
193                nftIDs: nftIDs,
194                salePaymentVaultType: salePaymentVaultType,
195                storefrontID: storefrontID,
196                salePrice: salePrice,
197                receiver: receiver,
198                discount: discount
199            )
200
201            // Store the NFT provider
202            self.nftProviderCapability = nftProviderCapability
203
204            // Check that the provider contains the NFT.
205            // We will check it again when the token is sold.
206            // We cannot move this into a function because initializers cannot call member functions.
207            let provider = self.nftProviderCapability.borrow()
208            assert(provider != nil, message: "cannot borrow nftProviderCapability")
209
210            // This will precondition assert if the token is not available.
211/*
212            let nft = provider!.borrowNFT(id: self.details.nftIDs)
213            assert(nft.isInstance(self.details.nftType), message: "token is not of specified type")*/
214
215        }
216    }
217
218        // StorefrontManager
219    // An interface for adding and removing Listings within a Storefront,
220    // intended for use by the Storefront's own
221    //
222    pub resource interface StorefrontManager {
223        // createListing
224        // Allows the Storefront owner to create and insert Listings.
225        //
226        pub fun createListing(
227            nftProviderCapability: Capability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>,
228            nftType: Type,
229            nftIDs: [UInt64],
230            receiver: Capability<&{FungibleToken.Receiver}>,
231            salePaymentVaultType: Type,
232            salePrice: UFix64,
233            discount: UFix64?
234        ): UInt64
235        // removeListing
236        // Allows the Storefront owner to remove any sale listing, acepted or not.
237        //
238        pub fun removeListing(listingResourceID: UInt64)
239    }
240
241    // StorefrontPublic
242    // An interface to allow listing and borrowing Listings, and purchasing items via Listings
243    // in a Storefront.
244    //
245    pub resource interface StorefrontPublic {
246        pub fun getListingIDs(): [UInt64]
247        pub fun borrowListing(listingResourceID: UInt64): &Listing{ListingPublic}?
248        pub fun cleanup(listingResourceID: UInt64)
249   }
250
251    // Storefront
252    // A resource that allows its owner to manage a list of Listings, and anyone to interact with them
253    // in order to query their details and purchase the NFTs that they represent.
254    //
255    pub resource Storefront : StorefrontManager, StorefrontPublic {
256        // The dictionary of Listing uuids to Listing resources.
257        access(self) var listings: @{UInt64: Listing}
258
259        // insert
260        // Create and publish a Listing for an NFT.
261        //
262         pub fun createListing(
263            nftProviderCapability: Capability<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic}>,
264            nftType: Type,
265            nftIDs: [UInt64],
266            receiver: Capability<&{FungibleToken.Receiver}>,
267            salePaymentVaultType: Type,
268            salePrice: UFix64,
269            discount: UFix64?
270         ): UInt64 {
271            let listing <- create Listing(
272                nftProviderCapability: nftProviderCapability,
273                nftType: nftType,
274                nftIDs: nftIDs,
275                salePaymentVaultType: salePaymentVaultType,
276                salePrice: salePrice,
277                storefrontID: self.uuid,
278                receiver: receiver,
279                discount: discount
280            )
281
282            let listingResourceID = listing.uuid
283            let listingPrice = listing.getDetails().salePrice
284
285            // Add the new listing to the dictionary.
286            let oldListing <- self.listings[listingResourceID] <- listing
287            // Note that oldListing will always be nil, but we have to handle it.
288
289            destroy oldListing
290
291            emit ListingAvailable(
292                storefrontAddress: self.owner?.address!,
293                listingResourceID: listingResourceID,
294                nftType: nftType,
295                nftIDs: nftIDs,
296                ftVaultType: salePaymentVaultType,
297                price: listingPrice
298            )
299
300            return listingResourceID
301        }
302        
303
304        // removeListing
305        // Remove a Listing that has not yet been purchased from the collection and destroy it.
306        //
307        pub fun removeListing(listingResourceID: UInt64) {
308            let listing <- self.listings.remove(key: listingResourceID)
309                ?? panic("missing Listing")
310    
311            // This will emit a ListingCompleted event.
312            destroy listing
313        }
314
315        // getListingIDs
316        // Returns an array of the Listing resource IDs that are in the collection
317        //
318        pub fun getListingIDs(): [UInt64] {
319            return self.listings.keys
320        }
321
322        // borrowSaleItem
323        // Returns a read-only view of the SaleItem for the given listingID if it is contained by this collection.
324        //
325        pub fun borrowListing(listingResourceID: UInt64): &Listing{ListingPublic}? {
326            if self.listings[listingResourceID] != nil {
327                return (&self.listings[listingResourceID] as &Listing{ListingPublic}?)
328            } else {
329                return nil
330            }
331        }
332
333        // cleanup
334        // Remove an listing *if* it has been purchased.
335        // Anyone can call, but at present it only benefits the account owner to do so.
336        // Kind purchasers can however call it if they like.
337        //
338        pub fun cleanup(listingResourceID: UInt64) {
339            pre {
340                self.listings[listingResourceID] != nil: "could not find listing with given id"
341            }
342
343            let listing <- self.listings.remove(key: listingResourceID)!
344            assert(listing.getDetails().purchased == true, message: "listing is not purchased, only admin can remove")
345            destroy listing
346        }
347
348        // destructor
349        //
350        destroy () {
351            destroy self.listings
352
353            // Let event consumers know that this storefront will no longer exist
354            emit StorefrontDestroyed(storefrontResourceID: self.uuid)
355        }
356
357        // constructor
358        //
359        init () {
360            self.listings <- {}
361
362            // Let event consumers know that this storefront exists
363            emit StorefrontInitialized(storefrontResourceID: self.uuid)
364        }
365    }
366
367    // createStorefront
368    // Make creating a Storefront publicly accessible.
369    //
370    pub fun createStorefront(): @Storefront {
371        return <-create Storefront()
372    }
373
374    init () {
375        self.StorefrontStoragePath = /storage/CarClubStorefront
376        self.StorefrontPublicPath = /public/CarClubStorefront
377
378        emit CarClubStorefrontInitialized()
379    }
380    
381
382
383}