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