Smart Contract
FindMarketDirectOfferSoft
A.097bafa4e0b48eef.FindMarketDirectOfferSoft
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import NonFungibleToken from 0x1d7e57aa55817448
4import MetadataViews from 0x1d7e57aa55817448
5import FindViews from 0x097bafa4e0b48eef
6import Clock from 0x097bafa4e0b48eef
7import Debug from 0x097bafa4e0b48eef
8import FIND from 0x097bafa4e0b48eef
9import FindMarket from 0x097bafa4e0b48eef
10import Profile from 0x097bafa4e0b48eef
11
12access(all) contract FindMarketDirectOfferSoft {
13
14 access(all) event DirectOffer(tenant: String, id: UInt64, saleID: UInt64, seller: Address, sellerName: String?, amount: UFix64, status: String, vaultType:String, nft: FindMarket.NFTInfo?, buyer:Address?, buyerName:String?, buyerAvatar:String?, endsAt: UFix64?, previousBuyer:Address?, previousBuyerName:String?)
15
16 access(all) entitlement Seller
17
18 access(all) resource SaleItem : FindMarket.SaleItem{
19
20 access(contract) var pointer: {FindViews.Pointer}
21 access(contract) var offerCallback: Capability<&MarketBidCollection>
22
23 access(contract) var directOfferAccepted:Bool
24 access(contract) var validUntil: UFix64?
25 access(contract) var saleItemExtraField: {String : AnyStruct}
26 access(contract) let totalRoyalties: UFix64
27
28 init(pointer: {FindViews.Pointer}, callback: Capability<&MarketBidCollection>, validUntil: UFix64?, saleItemExtraField: {String : AnyStruct}) {
29 self.pointer=pointer
30 self.offerCallback=callback
31 self.directOfferAccepted=false
32 self.validUntil=validUntil
33 self.saleItemExtraField=saleItemExtraField
34 self.totalRoyalties=self.pointer.getTotalRoyaltiesCut()
35 }
36
37
38 access(all) fun getId() : UInt64{
39 return self.pointer.getUUID()
40 }
41
42 access(contract) fun acceptDirectOffer() {
43 self.directOfferAccepted=true
44 }
45
46 //Here we do not get a vault back, it is sent in to the method itself
47 access(contract) fun acceptNonEscrowedBid() {
48 if !self.offerCallback.check() {
49 panic("Bidder unlinked the bid collection capability. Bidder Address : ".concat(self.offerCallback.address.toString()))
50 }
51 let pointer= self.pointer as! FindViews.AuthNFTPointer
52 self.offerCallback.borrow()!.acceptNonEscrowed(<- pointer.withdraw())
53 }
54
55 access(all) fun getRoyalty() : MetadataViews.Royalties {
56 return self.pointer.getRoyalty()
57 }
58
59 access(all) fun getFtType() : Type {
60 if !self.offerCallback.check() {
61 panic("Bidder unlinked the bid collection capability. Bidder Address : ".concat(self.offerCallback.address.toString()))
62 }
63 return self.offerCallback.borrow()!.getVaultType(self.getId())
64 }
65
66 access(all) fun getItemID() : UInt64 {
67 return self.pointer.id
68 }
69
70 access(all) fun getItemType() : Type {
71 return self.pointer.getItemType()
72 }
73
74 access(all) fun getAuction(): FindMarket.AuctionItem? {
75 return nil
76 }
77
78 access(all) fun getSaleType() : String {
79 if self.directOfferAccepted {
80 return "active_finished"
81 }
82 return "active_ongoing"
83 }
84
85 access(all) fun getListingType() : Type {
86 return Type<@SaleItem>()
87 }
88
89 access(all) fun getListingTypeIdentifier() : String {
90 return Type<@SaleItem>().identifier
91 }
92
93 access(all) fun getBalance() : UFix64 {
94 if !self.offerCallback.check() {
95 panic("Bidder unlinked the bid collection capability. Bidder Address : ".concat(self.offerCallback.address.toString()))
96 }
97 return self.offerCallback.borrow()!.getBalance(self.getId())
98 }
99
100 access(all) fun getSeller() : Address {
101 return self.pointer.owner()
102 }
103
104 access(all) fun getSellerName() : String? {
105 let address = self.pointer.owner()
106 return FIND.reverseLookup(address)
107 }
108
109 access(all) fun getBuyer() : Address? {
110 return self.offerCallback.address
111 }
112
113 access(all) fun getBuyerName() : String? {
114 if let name = FIND.reverseLookup(self.offerCallback.address) {
115 return name
116 }
117 return nil
118 }
119
120 access(all) fun toNFTInfo(_ detail: Bool) : FindMarket.NFTInfo{
121 return FindMarket.NFTInfo(self.pointer.getViewResolver(), id: self.pointer.id, detail:detail)
122 }
123
124 access(contract) fun setValidUntil(_ time: UFix64?) {
125 self.validUntil=time
126 }
127
128 access(all) fun getValidUntil() : UFix64? {
129 return self.validUntil
130 }
131
132 access(contract) fun setPointer(_ pointer: FindViews.AuthNFTPointer) {
133 self.pointer=pointer
134 }
135
136 access(contract) fun setCallback(_ callback: Capability<&MarketBidCollection>) {
137 self.offerCallback=callback
138 }
139
140 access(all) fun checkPointer() : Bool {
141 return self.pointer.valid()
142 }
143
144 access(all) fun checkSoulBound() : Bool {
145 return self.pointer.checkSoulBound()
146 }
147
148 access(all) fun getSaleItemExtraField() : {String : AnyStruct} {
149 return self.saleItemExtraField
150 }
151
152 access(contract) fun setSaleItemExtraField(_ field: {String : AnyStruct}) {
153 self.saleItemExtraField = field
154 }
155
156 access(all) fun getTotalRoyalties() : UFix64 {
157 return self.totalRoyalties
158 }
159
160 access(all) fun validateRoyalties() : Bool {
161 return self.totalRoyalties == self.pointer.getTotalRoyaltiesCut()
162 }
163
164 access(all) fun getDisplay() : MetadataViews.Display {
165 return self.pointer.getDisplay()
166 }
167
168 access(all) fun getNFTCollectionData() : MetadataViews.NFTCollectionData {
169 return self.pointer.getNFTCollectionData()
170 }
171 }
172
173 access(all) resource interface SaleItemCollectionPublic {
174 //fetch all the tokens in the collection
175 access(all) fun getIds(): [UInt64]
176 access(all) fun containsId(_ id: UInt64): Bool
177 access(contract) fun cancelBid(_ id: UInt64)
178 access(contract) fun registerIncreasedBid(_ id: UInt64)
179
180 //place a bid on a token
181 access(contract) fun registerBid(item: FindViews.ViewReadPointer, callback: Capability<&MarketBidCollection>, validUntil: UFix64?, saleItemExtraField: {String : AnyStruct})
182
183 access(contract) fun isAcceptedDirectOffer(_ id:UInt64) : Bool
184
185 access(contract) fun fulfillDirectOfferNonEscrowed(id:UInt64, vault: @{FungibleToken.Vault})
186
187 }
188
189 access(all) resource SaleItemCollection: SaleItemCollectionPublic, FindMarket.SaleItemCollectionPublic {
190 //is this the best approach now or just put the NFT inside the saleItem?
191 access(contract) var items: @{UInt64: SaleItem}
192
193 access(contract) let tenantCapability: Capability<&FindMarket.Tenant>
194
195 init (_ tenantCapability: Capability<&FindMarket.Tenant>) {
196 self.items <- {}
197 self.tenantCapability=tenantCapability
198 }
199
200 access(self) fun getTenant() : &FindMarket.Tenant {
201 if !self.tenantCapability.check() {
202 panic("Tenant client is not linked anymore")
203 }
204 return self.tenantCapability.borrow()!
205 }
206
207 access(contract) fun isAcceptedDirectOffer(_ id:UInt64) : Bool{
208
209 if !self.items.containsKey(id) {
210 panic("Invalid id=".concat(id.toString()))
211 }
212 let saleItem = self.borrow(id)
213
214 return saleItem.directOfferAccepted
215 }
216
217 access(all) fun getListingType() : Type {
218 return Type<@SaleItem>()
219 }
220
221 //this is called when a buyer cancel a direct offer
222 access(contract) fun cancelBid(_ id: UInt64) {
223 if !self.items.containsKey(id) {
224 panic("Invalid id=".concat(id.toString()))
225 }
226 let saleItem=self.borrow(id)
227
228 let tenant=self.getTenant()
229 let ftType= saleItem.getFtType()
230
231 let status="cancel"
232 let balance=saleItem.getBalance()
233 let buyer=saleItem.getBuyer()!
234 let buyerName=FIND.reverseLookup(buyer)
235 let profile = Profile.find(buyer)
236
237 var nftInfo:FindMarket.NFTInfo?=nil
238 if saleItem.checkPointer() {
239 nftInfo=saleItem.toNFTInfo(false)
240 }
241
242 emit DirectOffer(tenant:tenant.name, id: saleItem.getId(), saleID: saleItem.uuid, seller:self.owner!.address, sellerName: FIND.reverseLookup(self.owner!.address), amount: balance, status:status, vaultType: ftType.identifier, nft:nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), endsAt: saleItem.validUntil, previousBuyer:nil, previousBuyerName:nil)
243
244
245 destroy <- self.items.remove(key: id)
246 }
247
248 //The only thing we do here is basically register an event
249 access(contract) fun registerIncreasedBid(_ id: UInt64) {
250
251 if !self.items.containsKey(id) {
252 panic("Invalid id=".concat(id.toString()))
253 }
254 let saleItem=self.borrow(id)
255
256 let tenant=self.getTenant()
257 let nftType=saleItem.getItemType()
258 let ftType= saleItem.getFtType()
259
260 let actionResult=tenant.allowedAction(listingType: Type<@FindMarketDirectOfferSoft.SaleItem>(), nftType: nftType, ftType: ftType, action: FindMarket.MarketAction(listing:true, name: "increase bid in direct offer soft"), seller: self.owner!.address, buyer: saleItem.offerCallback.address)
261
262 if !actionResult.allowed {
263 panic(actionResult.message)
264 }
265
266 let status="active_offered"
267 let owner=self.owner!.address
268 let balance=saleItem.getBalance()
269 let buyer=saleItem.getBuyer()!
270 let buyerName=FIND.reverseLookup(buyer)
271 let profile = Profile.find(buyer)
272
273 let nftInfo=saleItem.toNFTInfo(true)
274
275 emit DirectOffer(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:owner, sellerName: FIND.reverseLookup(owner), amount: balance, status:status, vaultType: ftType.identifier, nft:nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), endsAt: saleItem.validUntil, previousBuyer:nil, previousBuyerName:nil)
276
277 }
278
279
280 //This is a function that buyer will call (via his bid collection) to register the bicCallback with the seller
281 access(contract) fun registerBid(item: FindViews.ViewReadPointer, callback: Capability<&MarketBidCollection>, validUntil: UFix64?, saleItemExtraField: {String : AnyStruct}) {
282
283 let id = item.getUUID()
284
285 //If there are no bids from anybody else before we need to make the item
286 if !self.items.containsKey(id) {
287 let saleItem <- create SaleItem(pointer: item, callback: callback, validUntil: validUntil, saleItemExtraField: saleItemExtraField)
288
289 let tenant=self.getTenant()
290 let nftType= saleItem.getItemType()
291 let ftType= saleItem.getFtType()
292
293 let actionResult=tenant.allowedAction(listingType: Type<@FindMarketDirectOfferSoft.SaleItem>(), nftType: saleItem.getItemType(), ftType: saleItem.getFtType(), action: FindMarket.MarketAction(listing:true, name: "bid in direct offer soft"), seller: self.owner!.address, buyer: callback.address)
294
295 if !actionResult.allowed {
296 panic(actionResult.message)
297 }
298 self.items[id] <-! saleItem
299 let saleItemRef=self.borrow(id)
300 let status="active_offered"
301 let owner=self.owner!.address
302 let balance=saleItemRef.getBalance()
303 let buyer=callback.address
304 let buyerName=FIND.reverseLookup(buyer)
305 let profile = Profile.find(buyer)
306
307 let nftInfo=saleItemRef.toNFTInfo(true)
308
309 emit DirectOffer(tenant:tenant.name, id: id, saleID: saleItemRef.uuid, seller:owner, sellerName: FIND.reverseLookup(owner), amount: balance, status:status, vaultType: ftType.identifier, nft:nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), endsAt: saleItemRef.validUntil, previousBuyer:nil, previousBuyerName:nil)
310
311
312 return
313 }
314
315
316 let saleItem=self.borrow(id)
317 if self.borrow(id).getBuyer()! == callback.address {
318 panic("You already have the latest bid on this item, use the incraseBid transaction")
319 }
320
321 let tenant=self.getTenant()
322 let nftType= saleItem.getItemType()
323 let ftType= saleItem.getFtType()
324
325 let actionResult=tenant.allowedAction(listingType: Type<@FindMarketDirectOfferSoft.SaleItem>(), nftType: nftType, ftType: ftType, action: FindMarket.MarketAction(listing:true, name: "bid in direct offer soft"), seller: self.owner!.address, buyer: callback.address)
326
327 if !actionResult.allowed {
328 panic(actionResult.message)
329 }
330
331 let balance=callback.borrow()?.getBalance(id) ?? panic("Bidder unlinked the bid collection capability. bidder address : ".concat(callback.address.toString()))
332
333 let currentBalance=saleItem.getBalance()
334 Debug.log("currentBalance=".concat(currentBalance.toString()).concat(" new bid is at=").concat(balance.toString()))
335 if currentBalance >= balance {
336 panic("There is already a higher bid on this item. Current bid : ".concat(currentBalance.toString()).concat(" . New bid is at : ").concat(balance.toString()))
337 }
338 let previousBuyer=saleItem.offerCallback.address
339 //somebody else has the highest item so we cancel it
340 saleItem.offerCallback.borrow()!.cancelBidFromSaleItem(id)
341 saleItem.setValidUntil(validUntil)
342 saleItem.setSaleItemExtraField(saleItemExtraField)
343 saleItem.setCallback(callback)
344
345 let status="active_offered"
346 let owner=self.owner!.address
347 let buyer=saleItem.getBuyer()!
348 let buyerName=FIND.reverseLookup(buyer)
349 let profile = Profile.find(buyer)
350
351 let nftInfo=saleItem.toNFTInfo(true)
352
353 let previousBuyerName = FIND.reverseLookup(previousBuyer)
354
355
356 emit DirectOffer(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:owner, sellerName: FIND.reverseLookup(owner), amount: balance, status:status, vaultType: ftType.identifier, nft:nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), endsAt: saleItem.validUntil, previousBuyer:previousBuyer, previousBuyerName:previousBuyerName)
357
358
359 }
360
361 //cancel will reject a direct offer
362 access(Seller) fun cancel(_ id: UInt64) {
363
364 if !self.items.containsKey(id) {
365 panic("Invalid id=".concat(id.toString()))
366 }
367
368 let saleItem=self.borrow(id)
369
370 let tenant=self.getTenant()
371 let ftType= saleItem.getFtType()
372
373
374 var status = "cancel_rejected"
375 let owner=self.owner!.address
376 let balance=saleItem.getBalance()
377 let buyer=saleItem.getBuyer()!
378 let buyerName=FIND.reverseLookup(buyer)
379 let profile = Profile.find(buyer)
380
381 var nftInfo:FindMarket.NFTInfo?=nil
382 if saleItem.checkPointer() {
383 nftInfo=saleItem.toNFTInfo(false)
384 }
385
386 emit DirectOffer(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:owner, sellerName: FIND.reverseLookup(owner), amount: balance, status:status, vaultType: ftType.identifier, nft:nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), endsAt: saleItem.validUntil, previousBuyer:nil, previousBuyerName:nil)
387
388 if !saleItem.offerCallback.check() {
389 panic("Seller unlinked the SaleItem collection capability. seller address : ".concat(saleItem.offerCallback.address.toString()))
390 }
391 saleItem.offerCallback.borrow()!.cancelBidFromSaleItem(id)
392 destroy <- self.items.remove(key: id)
393 }
394
395 access(Seller) fun acceptOffer(_ pointer: FindViews.AuthNFTPointer) {
396
397 let id = pointer.getUUID()
398
399 if !self.items.containsKey(id) {
400 panic("Invalid id=".concat(id.toString()))
401 }
402
403 let saleItem = self.borrow(id)
404
405 if saleItem.validUntil != nil && saleItem.validUntil! < Clock.time() {
406 panic("This direct offer is already expired")
407 }
408
409 let tenant=self.getTenant()
410 let nftType= saleItem.getItemType()
411 let ftType= saleItem.getFtType()
412
413 let actionResult=tenant.allowedAction(listingType: Type<@FindMarketDirectOfferSoft.SaleItem>(), nftType: nftType, ftType: ftType, action: FindMarket.MarketAction(listing:false, name: "accept offer in direct offer soft"), seller: self.owner!.address, buyer: saleItem.offerCallback.address)
414
415 if !actionResult.allowed {
416 panic(actionResult.message)
417 }
418
419 //Set the auth pointer in the saleItem so that it now can be fulfilled
420 saleItem.setPointer(pointer)
421 saleItem.acceptDirectOffer()
422
423 let status="active_accepted"
424 let owner=self.owner!.address
425 let balance=saleItem.getBalance()
426 let buyer=saleItem.getBuyer()!
427 let buyerName=FIND.reverseLookup(buyer)
428 let profile = Profile.find(buyer)
429
430 let nftInfo=saleItem.toNFTInfo(true)
431
432 emit DirectOffer(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:owner, sellerName: FIND.reverseLookup(owner), amount: balance, status:status, vaultType: ftType.identifier, nft:nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), endsAt: saleItem.validUntil, previousBuyer:nil, previousBuyerName:nil)
433
434
435 }
436
437 /// this is called from a bid when a seller accepts
438 access(contract) fun fulfillDirectOfferNonEscrowed(id:UInt64, vault: @{FungibleToken.Vault}) {
439
440 if !self.items.containsKey(id) {
441 panic("Invalid id=".concat(id.toString()))
442 }
443
444 let saleItem = self.borrow(id)
445 if !saleItem.directOfferAccepted {
446 panic("cannot fulfill a direct offer that is not accepted yet")
447 }
448
449 if vault.getType() != saleItem.getFtType() {
450 panic("The FT vault sent in to fulfill does not match the required type. Required Type : ".concat(saleItem.getFtType().identifier).concat(" . Sent-in vault type : ".concat(vault.getType().identifier)))
451 }
452
453 let tenant=self.getTenant()
454 let nftType= saleItem.getItemType()
455 let ftType= saleItem.getFtType()
456
457 let actionResult=tenant.allowedAction(listingType: Type<@FindMarketDirectOfferSoft.SaleItem>(), nftType: nftType, ftType: ftType, action: FindMarket.MarketAction(listing:false, name: "fulfill directOffer"), seller: self.owner!.address, buyer: saleItem.offerCallback.address)
458
459 if !actionResult.allowed {
460 panic(actionResult.message)
461 }
462
463 let cuts= tenant.getCuts(name: actionResult.name, listingType: Type<@FindMarketDirectOfferSoft.SaleItem>(), nftType: nftType, ftType: ftType)
464
465
466 let status="sold"
467 let owner=self.owner!.address
468 let balance=saleItem.getBalance()
469 let buyer=saleItem.getBuyer()!
470 let buyerName=FIND.reverseLookup(buyer)
471 let sellerName=FIND.reverseLookup(owner)
472 let profile = Profile.find(buyer)
473
474 let nftInfo=saleItem.toNFTInfo(true)
475
476 emit DirectOffer(tenant:tenant.name, id: saleItem.getId(), saleID: saleItem.uuid, seller:owner, sellerName: sellerName, amount: balance, status:status, vaultType: ftType.identifier, nft:nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), endsAt: saleItem.validUntil, previousBuyer:nil, previousBuyerName:nil)
477
478 let royalty=saleItem.getRoyalty()
479 saleItem.acceptNonEscrowedBid()
480
481 let resolved : {Address : String} = {}
482 resolved[buyer] = buyerName ?? ""
483 resolved[owner] = sellerName ?? ""
484 resolved[FindMarketDirectOfferSoft.account.address] = "find"
485 // Have to make sure the tenant always have the valid find name
486 resolved[FindMarket.tenantNameAddress[tenant.name]!] = tenant.name
487
488 FindMarket.pay(tenant: tenant.name, id:id, saleItem: saleItem, vault: <- vault, royalty:royalty, nftInfo: nftInfo, cuts:cuts, resolver: fun(address:Address): String? { return FIND.reverseLookup(address) }, resolvedAddress: resolved)
489
490 destroy <- self.items.remove(key: id)
491 }
492
493 access(all) fun getIds(): [UInt64] {
494 return self.items.keys
495 }
496
497 access(all) fun getRoyaltyChangedIds(): [UInt64] {
498 let ids : [UInt64] = []
499 for id in self.getIds() {
500 let item = self.borrow(id)
501 if !item.validateRoyalties() {
502 ids.append(id)
503 }
504 }
505 return ids
506 }
507
508 access(all) fun containsId(_ id: UInt64): Bool {
509 return self.items.containsKey(id)
510 }
511
512 access(all) fun borrow(_ id: UInt64): &SaleItem {
513 if !self.items.containsKey(id) {
514 panic("This id does not exist.".concat(id.toString()))
515 }
516 return (&self.items[id])!
517 }
518
519 access(all) fun borrowSaleItem(_ id: UInt64) : &{FindMarket.SaleItem} {
520 if !self.items.containsKey(id) {
521 panic("This id does not exist.".concat(id.toString()))
522 }
523 return (&self.items[id])!
524 }
525 }
526
527 /*
528 ==========================================================================
529 Bids are a collection/resource for storing the bids bidder made on leases
530 ==========================================================================
531 */
532
533 access(all) resource Bid : FindMarket.Bid {
534 access(contract) let from: Capability<&SaleItemCollection>
535 access(contract) let nftCap: Capability<&{NonFungibleToken.Receiver}>
536 access(contract) let itemUUID: UInt64
537
538 //this should reflect on what the above uuid is for
539 access(contract) let vaultType: Type
540 access(contract) var bidAt: UFix64
541 access(contract) var balance: UFix64 //This is what you bid for non escrowed bids
542 access(contract) let bidExtraField: {String : AnyStruct}
543
544 init(from: Capability<&SaleItemCollection>, itemUUID: UInt64, nftCap: Capability<&{NonFungibleToken.Receiver}>, vaultType:Type, nonEscrowedBalance:UFix64, bidExtraField: {String : AnyStruct}){
545 self.vaultType= vaultType
546 self.balance=nonEscrowedBalance
547 self.itemUUID=itemUUID
548 self.from=from
549 self.bidAt=Clock.time()
550 self.nftCap=nftCap
551 self.bidExtraField=bidExtraField
552 }
553
554 access(contract) fun setBidAt(_ time: UFix64) {
555 self.bidAt=time
556 }
557
558 access(contract) fun increaseBid(_ amount:UFix64) {
559 self.balance=self.balance+amount
560 }
561
562 access(all) fun getBalance() : UFix64 {
563 return self.balance
564 }
565
566 access(all) fun getSellerAddress() : Address {
567 return self.from.address
568 }
569
570 access(all) fun getBidExtraField() : {String : AnyStruct} {
571 return self.bidExtraField
572 }
573 }
574
575 access(all) resource interface MarketBidCollectionPublic {
576 access(all) fun getBalance(_ id: UInt64) : UFix64
577 access(all) fun getVaultType(_ id: UInt64) : Type
578 access(all) fun containsId(_ id: UInt64): Bool
579 access(contract) fun acceptNonEscrowed(_ nft: @{NonFungibleToken.NFT})
580 access(contract) fun cancelBidFromSaleItem(_ id: UInt64)
581 }
582
583 access(all) entitlement Buyer
584
585 //A collection stored for bidders/buyers
586 access(all) resource MarketBidCollection: MarketBidCollectionPublic, FindMarket.MarketBidCollectionPublic {
587
588 access(contract) var bids : @{UInt64: Bid}
589 access(contract) let receiver: Capability<&{FungibleToken.Receiver}>
590 access(contract) let tenantCapability: Capability<&FindMarket.Tenant>
591
592 //not sure we can store this here anymore. think it needs to be in every bid
593 init(receiver: Capability<&{FungibleToken.Receiver}>, tenantCapability: Capability<&FindMarket.Tenant>) {
594 self.bids <- {}
595 self.receiver=receiver
596 self.tenantCapability=tenantCapability
597 }
598
599 access(self) fun getTenant() : &FindMarket.Tenant {
600 if !self.tenantCapability.check() {
601 panic("Tenant client is not linked anymore")
602 }
603 return self.tenantCapability.borrow()!
604 }
605
606 //called from lease when auction is ended
607 access(contract) fun acceptNonEscrowed(_ nft: @{NonFungibleToken.NFT}) {
608 let id= nft.id
609 let bid <- self.bids.remove(key: nft.uuid) ?? panic("missing bid")
610 if !bid.nftCap.check() {
611 panic("Bidder unlinked the nft receiver capability. bidder address : ".concat(bid.nftCap.address.toString()))
612 }
613 bid.nftCap.borrow()!.deposit(token: <- nft)
614 destroy bid
615 }
616
617 access(all) fun getVaultType(_ id:UInt64) : Type {
618 return self.borrowBid(id).vaultType
619 }
620
621 access(all) fun getIds() : [UInt64] {
622 return self.bids.keys
623 }
624
625 access(all) fun containsId(_ id: UInt64) : Bool {
626 return self.bids.containsKey(id)
627 }
628
629 access(all) fun getBidType() : Type {
630 return Type<@Bid>()
631 }
632
633 access(Buyer) fun bid(item: FindViews.ViewReadPointer, amount: UFix64, vaultType:Type, nftCap: Capability<&{NonFungibleToken.Receiver}>, validUntil: UFix64?, saleItemExtraField: {String : AnyStruct}, bidExtraField: {String : AnyStruct}) {
634
635 // ensure it is not a 0 dollar listing
636 if amount <= 0.0 {
637 panic("Offer price should be greater than 0")
638 }
639
640 // ensure validUntil is valid
641 if validUntil != nil && validUntil! < Clock.time() {
642 panic("Valid until is before current time")
643 }
644
645 // check soul bound
646 if item.checkSoulBound() {
647 panic("This item is soul bounded and cannot be traded")
648 }
649
650 if self.owner!.address == item.owner() {
651 panic("You cannot bid on your own resource")
652 }
653
654 let uuid=item.getUUID()
655
656 if self.bids[uuid] != nil {
657 panic("You already have an bid for this item, use increaseBid on that bid")
658 }
659 let tenant=self.getTenant()
660
661 // Check if it is onefootball. If so, listing has to be at least $0.65 (DUC)
662 if tenant.name == "onefootball" {
663 // ensure it is not a 0 dollar listing
664 if amount <= 0.65 {
665 panic("Offer price should be greater than 0.65")
666 }
667 }
668
669 let from=getAccount(item.owner()).capabilities.get<&SaleItemCollection>(tenant.getPublicPath(Type<@SaleItemCollection>()))
670
671 let bid <- create Bid(from: from, itemUUID:uuid, nftCap: nftCap, vaultType: vaultType, nonEscrowedBalance:amount, bidExtraField: bidExtraField)
672 let saleItemCollection= from.borrow() ?? panic("Could not borrow sale item for id=".concat(uuid.toString()))
673 let callbackCapability =self.owner!.capabilities.get<&MarketBidCollection>(tenant.getPublicPath(Type<@MarketBidCollection>()))
674
675 let oldToken <- self.bids[uuid] <- bid
676 saleItemCollection.registerBid(item: item, callback: callbackCapability, validUntil: validUntil, saleItemExtraField: saleItemExtraField)
677 destroy oldToken
678 }
679
680 access(Buyer) fun fulfillDirectOffer(id:UInt64, vault: @{FungibleToken.Vault}) {
681
682 if self.bids[id] == nil {
683 panic( "You need to have a bid here already".concat(id.toString()))
684 }
685
686 let bid =self.borrowBid(id)
687 let saleItem=bid.from.borrow()!
688
689 if !saleItem.isAcceptedDirectOffer(id) {
690 panic("offer is not accepted yet")
691 }
692
693 saleItem.fulfillDirectOfferNonEscrowed(id:id, vault: <- vault)
694 }
695
696 access(Buyer) fun increaseBid(id: UInt64, increaseBy: UFix64) {
697 let bid =self.borrowBid(id)
698 bid.setBidAt(Clock.time())
699 bid.increaseBid(increaseBy)
700 if !bid.from.check() {
701 panic("Seller unlinked the SaleItem collection capability. seller address : ".concat(bid.from.address.toString()))
702 }
703 bid.from.borrow()!.registerIncreasedBid(id)
704 }
705
706 /// The users cancel a bid himself
707 access(Buyer) fun cancelBid(_ id: UInt64) {
708 let bid= self.borrowBid(id)
709 if !bid.from.check() {
710 panic("Seller unlinked the SaleItem collection capability. seller address : ".concat(bid.from.address.toString()))
711 }
712 bid.from.borrow()!.cancelBid(id)
713 self.cancelBidFromSaleItem(id)
714 }
715
716 //called from saleItem when things are cancelled
717 //if the bid is canceled from seller then we move the vault tokens back into your vault
718 access(contract) fun cancelBidFromSaleItem(_ id: UInt64) {
719 let bid <- self.bids.remove(key: id) ?? panic("missing bid")
720 destroy bid
721 }
722
723 access(all) fun borrowBid(_ id: UInt64): &Bid {
724 if !self.bids.containsKey(id) {
725 panic("This id does not exist.".concat(id.toString()))
726 }
727 return (&self.bids[id])!
728 }
729
730 access(all) fun borrowBidItem(_ id: UInt64): &{FindMarket.Bid} {
731 if !self.bids.containsKey(id) {
732 panic("This id does not exist.".concat(id.toString()))
733 }
734 return (&self.bids[id])!
735 }
736
737 access(all) fun getBalance(_ id: UInt64) : UFix64 {
738 let bid= self.borrowBid(id)
739 return bid.balance
740 }
741
742 }
743
744 //Create an empty lease collection that store your leases to a name
745 access(all) fun createEmptySaleItemCollection(_ tenantCapability: Capability<&FindMarket.Tenant>) : @SaleItemCollection {
746 return <- create SaleItemCollection(tenantCapability)
747 }
748
749 access(all) fun createEmptyMarketBidCollection(receiver: Capability<&{FungibleToken.Receiver}>, tenantCapability: Capability<&FindMarket.Tenant>) : @MarketBidCollection {
750 return <- create MarketBidCollection(receiver: receiver, tenantCapability:tenantCapability)
751 }
752
753 access(all) fun getSaleItemCapability(marketplace:Address, user:Address) : Capability<&SaleItemCollection>? {
754 if FindMarket.getTenantCapability(marketplace) == nil {
755 panic("Invalid tenant")
756 }
757 if let tenant=FindMarket.getTenantCapability(marketplace)!.borrow() {
758 let cap = getAccount(user).capabilities.get<&SaleItemCollection>(tenant.getPublicPath(Type<@SaleItemCollection>()))
759 if !cap.check() {
760 return nil
761 }
762 return cap
763 }
764 return nil
765 }
766
767 access(all) fun getBidCapability( marketplace:Address, user:Address) : Capability<&MarketBidCollection>? {
768 if FindMarket.getTenantCapability(marketplace) == nil {
769 panic("Invalid tenant")
770 }
771 if let tenant=FindMarket.getTenantCapability(marketplace)!.borrow() {
772 let cap = getAccount(user).capabilities.get<&MarketBidCollection>(tenant.getPublicPath(Type<@MarketBidCollection>()))
773 if !cap.check() {
774 return nil
775 }
776 return cap
777 }
778 return nil
779 }
780
781 init() {
782 FindMarket.addSaleItemType(Type<@SaleItem>())
783 FindMarket.addSaleItemCollectionType(Type<@SaleItemCollection>())
784 FindMarket.addMarketBidType(Type<@Bid>())
785 FindMarket.addMarketBidCollectionType(Type<@MarketBidCollection>())
786 }
787}
788