Smart Contract

FindLeaseMarketSale

A.097bafa4e0b48eef.FindLeaseMarketSale

Deployed

1d ago
Feb 26, 2026, 03:12:51 AM UTC

Dependents

13 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import FindViews from 0x097bafa4e0b48eef
3import Clock from 0x097bafa4e0b48eef
4import FIND from 0x097bafa4e0b48eef
5import FindLeaseMarket from 0x097bafa4e0b48eef
6import FindMarket from 0x097bafa4e0b48eef
7import Debug from 0x097bafa4e0b48eef
8
9/*
10A Find Market for direct sales
11*/
12
13access(all) contract FindLeaseMarketSale {
14    
15    // A seller can list, delist and relist leases for sale
16    access(all) entitlement Seller
17
18    access(all) event Sale(tenant: String, id: UInt64, saleID: UInt64, seller: Address, sellerName: String?, amount: UFix64, status: String, vaultType:String, leaseInfo: FindLeaseMarket.LeaseInfo?, buyer:Address?, buyerName:String?, buyerAvatar: String?, endsAt:UFix64?)
19
20    // A sale item for a direct sale
21    access(all) resource SaleItem : FindLeaseMarket.SaleItem{
22        //this is set when bought so that pay will work
23        access(self) var buyer: Address?
24
25        access(contract) let vaultType: Type //The type of vault to use for this sale Item
26        access(contract) var pointer: FindLeaseMarket.AuthLeasePointer
27
28        //this field is set if this is a saleItem
29        access(contract) var salePrice: UFix64
30        access(contract) var validUntil: UFix64?
31        access(contract) let saleItemExtraField: {String : AnyStruct}
32
33        init(pointer: FindLeaseMarket.AuthLeasePointer, vaultType: Type, price:UFix64, validUntil: UFix64?, saleItemExtraField: {String : AnyStruct}) {
34            self.vaultType=vaultType
35            self.pointer=pointer
36            self.salePrice=price
37            self.buyer=nil
38            self.validUntil=validUntil
39            self.saleItemExtraField=saleItemExtraField
40        }
41
42        access(all) fun getSaleType() : String {
43            return "active_listed"
44        }
45
46        access(all) fun getListingType() : Type {
47            return Type<@SaleItem>()
48        }
49
50        access(all) fun getListingTypeIdentifier(): String {
51            return Type<@SaleItem>().identifier
52        }
53
54        access(account) fun setBuyer(_ address:Address) {
55            self.buyer=address
56        }
57
58        access(all) fun getBuyer(): Address? {
59            return self.buyer
60        }
61
62        access(all) fun getBuyerName() : String? {
63            if let address = self.buyer {
64                return FIND.reverseLookup(address)
65            }
66            return nil
67        }
68
69        access(all) fun getLeaseName() : String {
70            return self.pointer.name
71        }
72
73        access(all) fun getItemType() : Type {
74            return Type<@FIND.Lease>()
75        }
76
77        access(all) fun getId() : UInt64 {
78            return self.pointer.getUUID()
79        }
80
81        access(all) fun getSeller() : Address {
82            return self.pointer.owner()
83        }
84
85        access(all) fun getSellerName() : String? {
86            let address = self.pointer.owner()
87            return FIND.reverseLookup(address)
88        }
89
90        access(all) fun getBalance() : UFix64 {
91            return self.salePrice
92        }
93
94        access(all) fun getAuction(): FindLeaseMarket.AuctionItem? {
95            return nil
96        }
97
98        access(all) fun getFtType() : Type  {
99            return self.vaultType
100        }
101
102        access(contract) fun setValidUntil(_ time: UFix64?) {
103            self.validUntil=time
104        }
105
106        access(all) fun getValidUntil() : UFix64? {
107            return self.validUntil
108        }
109
110        access(all) fun toLeaseInfo() : FindLeaseMarket.LeaseInfo {
111            return FindLeaseMarket.LeaseInfo(self.pointer)
112        }
113
114        access(all) fun checkPointer() : Bool {
115            return self.pointer.valid()
116        }
117
118        access(all) fun getSaleItemExtraField() : {String : AnyStruct} {
119            return self.saleItemExtraField
120        }
121
122    }
123
124    access(all) resource interface SaleItemCollectionPublic {
125        //fetch all the tokens in the collection
126        access(all) fun getNameSales(): [String]
127        access(all) fun containsNameSale(_ name: String): Bool
128        access(all) fun borrowSaleItem(_ name: String) : &{FindLeaseMarket.SaleItem}
129        access(all) fun buy(name: String, vault: @{FungibleToken.Vault}, to: Address)
130    }
131
132    access(all) resource SaleItemCollection: SaleItemCollectionPublic, FindLeaseMarket.SaleItemCollectionPublic {
133        //is this the best approach now or just put the NFT inside the saleItem?
134        access(contract) var items: @{String: SaleItem}
135
136        access(contract) let tenantCapability: Capability<&FindMarket.Tenant>
137
138        init (_ tenantCapability: Capability<&FindMarket.Tenant>) {
139            self.items <- {}
140            self.tenantCapability=tenantCapability
141        }
142
143        access(self) fun getTenant() : &FindMarket.Tenant {
144            pre{
145                self.tenantCapability.check() : "Tenant client is not linked anymore"
146            }
147            return self.tenantCapability.borrow()!
148        }
149
150        access(all) fun getListingType() : Type {
151            return Type<@SaleItem>()
152        }
153
154        access(all) fun buy(name: String, vault: @{FungibleToken.Vault}, to: Address)  {
155            pre {
156                self.items.containsKey(name) : "Invalid name=".concat(name)
157                self.owner!.address != to : "You cannot buy your own listing"
158            }
159
160            let saleItem=self.borrow(name)
161
162            if saleItem.salePrice != vault.balance {
163                panic("Incorrect balance sent in vault. Expected ".concat(saleItem.salePrice.toString()).concat(" got ").concat(vault.balance.toString()))
164            }
165
166            if saleItem.validUntil != nil && saleItem.validUntil! < Clock.time() {
167                panic("This sale item listing is already expired")
168            }
169
170            if saleItem.vaultType != vault.getType() {
171                panic("This item can be baught using ".concat(saleItem.vaultType.identifier).concat(" you have sent in ").concat(vault.getType().identifier))
172            }
173
174            let actionResult=self.getTenant().allowedAction(listingType: Type<@FindLeaseMarketSale.SaleItem>(), nftType: saleItem.getItemType(), ftType: saleItem.getFtType(), action: FindMarket.MarketAction(listing:false, name:"buy lease for sale"), seller: self.owner!.address, buyer: to)
175
176            if !actionResult.allowed {
177                panic(actionResult.message)
178            }
179
180            let cuts= self.getTenant().getCuts(name: actionResult.name, listingType: Type<@FindLeaseMarketSale.SaleItem>(), nftType: saleItem.getItemType(), ftType: saleItem.getFtType())
181
182            let ftType=saleItem.vaultType
183            let owner=saleItem.getSeller()
184            let leaseInfo= saleItem.toLeaseInfo()
185
186            let soldFor=saleItem.getBalance()
187            saleItem.setBuyer(to)
188            let buyer=to
189            let buyerName=FIND.reverseLookup(buyer)
190            let profile = FIND.lookup(buyer.toString())
191
192            saleItem.pointer.move(to: to)
193
194            FindLeaseMarket.pay(tenant:self.getTenant().name, leaseName:name, saleItem: saleItem, vault: <- vault, leaseInfo:leaseInfo, cuts:cuts)
195
196            emit Sale(tenant:self.getTenant().name, id: saleItem.getId(), saleID: saleItem.uuid, seller:owner, sellerName: FIND.reverseLookup(owner), amount: soldFor, status:"sold", vaultType: ftType.identifier, leaseInfo:leaseInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile?.getAvatar() ,endsAt:saleItem.validUntil)
197
198            destroy <- self.items.remove(key: name)
199        }
200
201        access(Seller) fun listForSale(pointer: FindLeaseMarket.AuthLeasePointer, vaultType: Type, directSellPrice:UFix64, validUntil: UFix64?, extraField: {String:AnyStruct}) {
202            Debug.log("foo")
203
204            // ensure it is not a 0 dollar listing
205            if directSellPrice <= 0.0 {
206                panic("Listing price should be greater than 0")
207            }
208
209            if validUntil != nil && validUntil! < Clock.time() {
210                panic("Valid until is before current time")
211            }
212
213            // What happends if we relist
214            let saleItem <- create SaleItem(pointer: pointer, vaultType:vaultType, price: directSellPrice, validUntil: validUntil, saleItemExtraField:extraField)
215
216            let actionResult=self.getTenant().allowedAction(listingType: Type<@FindLeaseMarketSale.SaleItem>(), nftType: saleItem.getItemType(), ftType: saleItem.getFtType(), action: FindMarket.MarketAction(listing:true, name:"list lease for sale"), seller: self.owner!.address, buyer: nil)
217
218            if !actionResult.allowed {
219                panic(actionResult.message)
220            }
221
222            let owner=self.owner!.address
223            emit Sale(tenant: self.getTenant().name, id: saleItem.getId(), saleID: saleItem.uuid, seller:owner, sellerName: FIND.reverseLookup(owner), amount: saleItem.salePrice, status: "active_listed", vaultType: vaultType.identifier, leaseInfo:saleItem.toLeaseInfo(), buyer: nil, buyerName:nil, buyerAvatar:nil, endsAt:saleItem.validUntil)
224            let old <- self.items[pointer.name] <- saleItem
225            destroy old
226
227        }
228
229        access(Seller) fun delist(_ name: String) {
230            pre {
231                self.items.containsKey(name) : "Unknown name lease=".concat(name)
232            }
233
234            let saleItem <- self.items.remove(key: name)!
235
236            if saleItem.checkPointer() {
237                let actionResult=self.getTenant().allowedAction(listingType: Type<@FindLeaseMarketSale.SaleItem>(), nftType: saleItem.getItemType(), ftType: saleItem.getFtType(), action: FindMarket.MarketAction(listing:false, name:"delist lease for sale"), seller: nil, buyer: nil)
238
239                if !actionResult.allowed {
240                    panic(actionResult.message)
241                }
242                let owner=self.owner!.address
243                emit Sale(tenant:self.getTenant().name, id: saleItem.getId(), saleID: saleItem.uuid, seller:owner, sellerName:FIND.reverseLookup(owner), amount: saleItem.salePrice, status: "cancel", vaultType: saleItem.vaultType.identifier,leaseInfo: saleItem.toLeaseInfo(), buyer:nil, buyerName:nil, buyerAvatar:nil, endsAt:saleItem.validUntil)
244                destroy saleItem
245                return
246            }
247
248            let owner=self.owner!.address
249            if !saleItem.checkPointer() {
250                emit Sale(tenant:self.getTenant().name, id: saleItem.getId(), saleID: saleItem.uuid, seller:owner, sellerName:FIND.reverseLookup(owner), amount: saleItem.salePrice, status: "cancel", vaultType: saleItem.vaultType.identifier,leaseInfo: nil, buyer:nil, buyerName:nil, buyerAvatar:nil, endsAt:saleItem.validUntil)
251            } else {
252                emit Sale(tenant:self.getTenant().name, id: saleItem.getId(), saleID: saleItem.uuid, seller:owner, sellerName:FIND.reverseLookup(owner), amount: saleItem.salePrice, status: "cancel", vaultType: saleItem.vaultType.identifier,leaseInfo: saleItem.toLeaseInfo(), buyer:nil, buyerName:nil, buyerAvatar:nil, endsAt:saleItem.validUntil)
253            }
254            destroy saleItem
255        }
256
257        access(all) fun getNameSales(): [String] {
258            return self.items.keys
259        }
260
261        access(all) fun containsNameSale(_ name: String): Bool {
262            return self.items.containsKey(name)
263        }
264
265        access(all) fun borrow(_ name: String): &SaleItem {
266            return (&self.items[name])!
267        }
268
269        access(all) fun borrowSaleItem(_ name: String) : &{FindLeaseMarket.SaleItem} {
270            pre{
271                self.items.containsKey(name) : "This name sale does not exist : ".concat(name)
272            }
273            return (&self.items[name])!
274        }
275
276    }
277
278    //Create an empty lease collection that store your leases to a name
279    access(all) fun createEmptySaleItemCollection(_ tenantCapability: Capability<&FindMarket.Tenant>) : @SaleItemCollection {
280        return <- create SaleItemCollection(tenantCapability)
281    }
282
283    access(all) fun getSaleItemCapability(marketplace:Address, user:Address) : Capability<&{FindLeaseMarketSale.SaleItemCollectionPublic, FindLeaseMarket.SaleItemCollectionPublic}>? {
284        if FindMarket.getTenantCapability(marketplace) == nil {
285            panic("Invalid tenant")
286        }
287        if let tenant=FindMarket.getTenantCapability(marketplace)!.borrow() {
288            return getAccount(user).capabilities.get<&{FindLeaseMarketSale.SaleItemCollectionPublic, FindLeaseMarket.SaleItemCollectionPublic}>(tenant.getPublicPath(Type<@SaleItemCollection>()))!
289        }
290        return nil
291    }
292
293    init() {
294        FindLeaseMarket.addSaleItemType(Type<@SaleItem>())
295        FindLeaseMarket.addSaleItemCollectionType(Type<@SaleItemCollection>())
296    }
297}
298