Smart Contract
FindLeaseMarketSale
A.097bafa4e0b48eef.FindLeaseMarketSale
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