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