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