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