Smart Contract
CarClubStorefront
A.f887ece39166906e.CarClubStorefront
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}