@flowtyio/flow-contracts 0.0.11 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,758 @@
1
+ import "FungibleToken"
2
+ import "FlowStorageFees"
3
+ import "FlowToken"
4
+ import "NonFungibleToken"
5
+ import "MetadataViews"
6
+ import "FeeEstimator"
7
+
8
+ // LostAndFound
9
+ // One big problem on the flow blockchain is how to handle accounts that are
10
+ // not configured to receive assets that you want to send. Currently,
11
+ // lots of platforms have to create their own escrow for people to redeem. If not an
12
+ // escrow, accounts might instead be skipped for things like an airtdrop
13
+ // because they aren't able to receive the assets they should have gotten.
14
+ // LostAndFound is meant to solve that problem, giving a central easy to use place to send
15
+ // and redeem items
16
+ //
17
+ // The LostAndFound is split into a few key components:
18
+ // Ticket - Tickets contain the resource which can be redeemed by a user. Everything else is organization around them.
19
+ // Bin - Bins sort tickets by their type. If two ExampleNFT.NFT items are deposited, there would be two tickets made.
20
+ // Those two tickets would be put in the same Bin because they are the same type
21
+ // Shelf - Shelves organize bins by address. When a resource is deposited into the LostAndFound, its receiver shelf is
22
+ // located, then the appropriate bin is picked for the item to go to. If the bin doesn't exist yet, a new one is made.
23
+ //
24
+ // In order for an account to redeem an item, they have to supply a receiver which matches the address of the ticket's redeemer
25
+ // For ease of use, there are three supported receivers:
26
+ // - NonFunigibleToken.Receiver
27
+ // - FungibleToken.Receiver
28
+ // - LostAndFound.ResourceReceiver (This is a placeholder so that non NFT and FT resources can be utilized here)
29
+ pub contract LostAndFound {
30
+ access(contract) let storageFees: {UInt64: UFix64}
31
+
32
+ pub let LostAndFoundPublicPath: PublicPath
33
+ pub let LostAndFoundStoragePath: StoragePath
34
+ pub let DepositorPublicPath: PublicPath
35
+ pub let DepositorStoragePath: StoragePath
36
+
37
+ pub event TicketDeposited(redeemer: Address, ticketID: UInt64, type: Type, memo: String?, name: String?, description: String?, thumbnail: String?)
38
+ pub event TicketRedeemed(redeemer: Address, ticketID: UInt64, type: Type)
39
+ pub event BinDestroyed(redeemer: Address, type: Type)
40
+ pub event ShelfDestroyed(redeemer: Address)
41
+
42
+ pub event DepositorCreated(uuid: UInt64)
43
+ pub event DepositorBalanceLow(uuid: UInt64, threshold: UFix64, balance: UFix64)
44
+ pub event DepositorTokensAdded(uuid: UInt64, tokens: UFix64, balance: UFix64)
45
+ pub event DepositorTokensWithdrawn(uuid: UInt64, tokens: UFix64, balance: UFix64)
46
+
47
+ // Placeholder receiver so that any resource can be supported, not just FT and NFT Receivers
48
+ pub resource interface AnyResourceReceiver {
49
+ pub fun deposit(resource: @AnyResource)
50
+ }
51
+
52
+ pub resource DepositEstimate {
53
+ pub var item: @AnyResource?
54
+ pub let storageFee: UFix64
55
+
56
+ init(item: @AnyResource, storageFee: UFix64) {
57
+ self.item <- item
58
+ self.storageFee = storageFee
59
+ }
60
+
61
+ pub fun withdraw(): @AnyResource {
62
+ let resource <- self.item <- nil
63
+ return <-resource!
64
+ }
65
+
66
+ destroy() {
67
+ pre {
68
+ self.item == nil: "cannot destroy with non-null item"
69
+ }
70
+
71
+ destroy self.item
72
+ }
73
+ }
74
+
75
+ // Tickets are the resource that hold items to be redeemed. They carry with them:
76
+ // - item: The Resource which has been deposited to be withdrawn/redeemed
77
+ // - memo: An optional message to attach to this ticket
78
+ // - redeemer: The address which is allowed to withdraw the item from this ticket
79
+ // - redeemed: Whether the ticket has been redeemed. This can only be set by the LostAndFound contract
80
+ pub resource Ticket {
81
+ // The item to be redeemed
82
+ access(contract) var item: @AnyResource?
83
+ // An optional message to attach to this item.
84
+ pub let memo: String?
85
+ // an optional Display view so that frontend's that borrow this ticket know how to show it
86
+ pub let display: MetadataViews.Display?
87
+ // The address that it allowed to withdraw the item fromt this ticket
88
+ pub let redeemer: Address
89
+ //The type of the resource (non-optional) so that bins can represent the true type of an item
90
+ pub let type: Type
91
+ // State maintained by LostAndFound
92
+ pub var redeemed: Bool
93
+
94
+ // flow token amount used to store this ticket is returned when the ticket is redeemed
95
+ access(contract) let flowTokenRepayment: Capability<&FlowToken.Vault{FungibleToken.Receiver}>?
96
+
97
+ init (item: @AnyResource, memo: String?, display: MetadataViews.Display?, redeemer: Address, flowTokenRepayment: Capability<&FlowToken.Vault{FungibleToken.Receiver}>?) {
98
+ self.type = item.getType()
99
+ self.item <- item
100
+ self.memo = memo
101
+ self.display = display
102
+ self.redeemer = redeemer
103
+ self.redeemed = false
104
+
105
+ self.flowTokenRepayment = flowTokenRepayment
106
+ }
107
+
108
+ pub fun itemType(): Type {
109
+ return self.type
110
+ }
111
+
112
+ pub fun checkItem(): Bool {
113
+ return self.item != nil
114
+ }
115
+
116
+ // A function to get depositor address / flow Repayment address
117
+ pub fun getFlowRepaymentAddress() : Address? {
118
+ return self.flowTokenRepayment?.address
119
+ }
120
+
121
+ // If this is an instance of NFT, return the id , otherwise return nil
122
+ pub fun getNonFungibleTokenID() : UInt64? {
123
+ if self.type.isSubtype(of: Type<@NonFungibleToken.NFT>()) {
124
+ let ref = (&self.item as auth &AnyResource?)!
125
+ let nft = ref as! &NonFungibleToken.NFT
126
+ return nft.id
127
+ }
128
+ return nil
129
+ }
130
+
131
+ // If this is an instance of FT, return the vault balance , otherwise return nil
132
+ pub fun getFungibleTokenBalance() : UFix64? {
133
+ if self.type.isSubtype(of: Type<@FungibleToken.Vault>()) {
134
+ let ref = (&self.item as auth &AnyResource?)!
135
+ let ft = ref as! &FungibleToken.Vault
136
+ return ft.balance
137
+ }
138
+ return nil
139
+ }
140
+
141
+ pub fun withdraw(receiver: Capability) {
142
+ pre {
143
+ receiver.address == self.redeemer: "receiver address and redeemer must match"
144
+ !self.redeemed: "already redeemed"
145
+ }
146
+
147
+ var redeemableItem <- self.item <- nil
148
+ let cap = receiver.borrow<&AnyResource>()!
149
+
150
+ if cap.isInstance(Type<@NonFungibleToken.Collection>()) {
151
+ let target = receiver.borrow<&AnyResource{NonFungibleToken.CollectionPublic}>()!
152
+ let token <- redeemableItem as! @NonFungibleToken.NFT?
153
+ self.redeemed = true
154
+ emit TicketRedeemed(redeemer: self.redeemer, ticketID: self.uuid, type: token.getType())
155
+ target.deposit(token: <- token!)
156
+ return
157
+ } else if cap.isInstance(Type<@FungibleToken.Vault>()) {
158
+ let target = receiver.borrow<&AnyResource{FungibleToken.Receiver}>()!
159
+ let token <- redeemableItem as! @FungibleToken.Vault?
160
+ self.redeemed = true
161
+ emit TicketRedeemed(redeemer: self.redeemer, ticketID: self.uuid, type: token.getType())
162
+ target.deposit(from: <- token!)
163
+ return
164
+ } else if cap.isInstance(Type<@AnyResource{LostAndFound.AnyResourceReceiver}>()) {
165
+ let target = receiver.borrow<&{LostAndFound.AnyResourceReceiver}>()!
166
+ self.redeemed = true
167
+ emit TicketRedeemed(redeemer: self.redeemer, ticketID: self.uuid, type: redeemableItem.getType())
168
+ target.deposit(resource: <- redeemableItem)
169
+ return
170
+ } else{
171
+ panic("cannot redeem resource to receiver")
172
+ }
173
+ }
174
+
175
+ // we need to be able to take our item back for storage cost estimation
176
+ // otherwise we can't actually deposit a ticket
177
+ access(account) fun takeItem(): @AnyResource {
178
+ self.redeemed = true
179
+ var redeemableItem <- self.item <- nil
180
+ return <-redeemableItem!
181
+ }
182
+
183
+ // destructon is only allowed if the ticket has been redeemed and the underlying item is a our dummy resource
184
+ destroy () {
185
+ pre {
186
+ self.redeemed: "Ticket has not been redeemed"
187
+ self.item == nil: "can only destroy if not holding any item"
188
+ }
189
+
190
+ LostAndFound.storageFees.remove(key: self.uuid)
191
+ destroy <-self.item
192
+ }
193
+ }
194
+
195
+
196
+ // A Bin is a resource that gathers tickets whos item have the same type.
197
+ // For instance, if two TopShot Moments are deposited to the same redeemer, only one bin
198
+ // will be made which will contain both tickets to redeem each individual moment.
199
+ pub resource Bin {
200
+ access(contract) let tickets: @{UInt64:Ticket}
201
+ access(contract) let type: Type
202
+
203
+ pub let flowTokenRepayment: Capability<&{FungibleToken.Receiver}>?
204
+
205
+ init (type: Type, flowTokenRepayment: Capability<&{FungibleToken.Receiver}>?) {
206
+ self.tickets <- {}
207
+ self.type = type
208
+ self.flowTokenRepayment = flowTokenRepayment
209
+ }
210
+
211
+ pub fun borrowTicket(id: UInt64): &LostAndFound.Ticket? {
212
+ return &self.tickets[id] as &LostAndFound.Ticket?
213
+ }
214
+
215
+ pub fun borrowAllTicketsByType(): [&LostAndFound.Ticket] {
216
+ let tickets: [&LostAndFound.Ticket] = []
217
+ let ids = self.tickets.keys
218
+ for id in ids {
219
+ tickets.append(self.borrowTicket(id: id)!)
220
+ }
221
+
222
+ return tickets
223
+ }
224
+
225
+ // deposit a ticket to this bin. The item type must match this bin's item type.
226
+ // this function is not public because if it were there would be a way to get around
227
+ // deposit fees
228
+ access(contract) fun deposit(ticket: @LostAndFound.Ticket) {
229
+ pre {
230
+ ticket.itemType() == self.type: "ticket and bin types must match"
231
+ ticket.item != nil: "nil item not allowed"
232
+ }
233
+
234
+ let redeemer = ticket.redeemer
235
+ let ticketID = ticket.uuid
236
+ let memo = ticket.memo
237
+
238
+ let name = ticket.display?.name
239
+ let description = ticket.display?.description
240
+ let thumbnail = ticket.display?.thumbnail?.uri()
241
+
242
+ self.tickets[ticket.uuid] <-! ticket
243
+ emit TicketDeposited(redeemer: redeemer, ticketID: ticketID, type: self.type, memo: memo, name: name, description: description, thumbnail: thumbnail)
244
+ }
245
+
246
+ pub fun getTicketIDs(): [UInt64] {
247
+ return self.tickets.keys
248
+ }
249
+
250
+ access(contract) fun withdrawTicket(ticketID: UInt64): @LostAndFound.Ticket {
251
+ let ticket <- self.tickets.remove(key: ticketID)
252
+ return <- ticket!
253
+ }
254
+
255
+ destroy () {
256
+ destroy <-self.tickets
257
+ LostAndFound.storageFees.remove(key: self.uuid)
258
+ }
259
+ }
260
+
261
+ // A shelf is our top-level organization resource.
262
+ // It groups bins by type to help make discovery of the assets that a
263
+ // redeeming address can claim.
264
+ pub resource Shelf {
265
+ access(self) let bins: @{String: Bin}
266
+ access(self) let identifierToType: {String: Type}
267
+ access(self) let redeemer: Address
268
+ access(contract) let flowTokenRepayment: Capability<&{FungibleToken.Receiver}>?
269
+
270
+ init (redeemer: Address, flowTokenRepayment: Capability<&{FungibleToken.Receiver}>?) {
271
+ self.bins <- {}
272
+ self.identifierToType = {}
273
+ self.redeemer = redeemer
274
+ self.flowTokenRepayment = flowTokenRepayment
275
+ }
276
+
277
+ pub fun getOwner(): Address {
278
+ return self.owner!.address
279
+ }
280
+
281
+ pub fun getRedeemableTypes(): [Type] {
282
+ let types: [Type] = []
283
+ for k in self.bins.keys {
284
+ let t = self.identifierToType[k]!
285
+ if t != nil {
286
+ types.append(t)
287
+ }
288
+ }
289
+ return types
290
+ }
291
+
292
+ pub fun hasType(type: Type): Bool {
293
+ return self.bins[type.identifier] != nil
294
+ }
295
+
296
+ pub fun borrowBin(type: Type): &LostAndFound.Bin? {
297
+ return &self.bins[type.identifier] as &LostAndFound.Bin?
298
+ }
299
+
300
+ access(contract) fun ensureBin(type: Type, flowTokenRepayment: Capability<&{FungibleToken.Receiver}>?): &Bin {
301
+ if !self.bins.containsKey(type.identifier) {
302
+ let storageBefore = LostAndFound.account.storageUsed
303
+ let bin <- create Bin(type: type, flowTokenRepayment: flowTokenRepayment)
304
+ let uuid = bin.uuid
305
+ let oldValue <- self.bins.insert(key: type.identifier, <-bin)
306
+ LostAndFound.storageFees[uuid] = FeeEstimator.storageUsedToFlowAmount(LostAndFound.account.storageUsed - storageBefore)
307
+ self.identifierToType[type.identifier] = type
308
+ destroy oldValue
309
+ }
310
+
311
+ return (&self.bins[type.identifier] as &LostAndFound.Bin?)!
312
+ }
313
+
314
+ access(contract) fun deposit(ticket: @LostAndFound.Ticket, flowTokenRepayment: Capability<&{FungibleToken.Receiver}>?) {
315
+ // is there a bin for this yet?
316
+ let type = ticket.itemType()
317
+ let bin = self.ensureBin(type: type, flowTokenRepayment: flowTokenRepayment)
318
+ bin.deposit(ticket: <-ticket)
319
+ }
320
+
321
+
322
+ // Redeem all the tickets of a given type. This is just a convenience function
323
+ // so that a redeemer doesn't have to coordinate redeeming each ticket individually
324
+ // Only one of the three receiver options can be specified, and an optional maximum number of tickets
325
+ // to redeem can be picked to prevent gas issues in case there are large numbers of tickets to be
326
+ // redeemed at once.
327
+ pub fun redeemAll(type: Type, max: Int?, receiver: Capability) {
328
+ pre {
329
+ receiver.address == self.redeemer: "receiver must match the redeemer of this shelf"
330
+ self.bins.containsKey(type.identifier): "no bin for provided type"
331
+ }
332
+
333
+ var count = 0
334
+ let borrowedBin = self.borrowBin(type: type)!
335
+ for key in borrowedBin.getTicketIDs() {
336
+ if max != nil && max == count {
337
+ return
338
+ }
339
+
340
+ self.redeem(type: type, ticketID: key, receiver: receiver)
341
+ count = count + 1
342
+ }
343
+ }
344
+
345
+ // Redeem a specific ticket instead of all of a certain type.
346
+ pub fun redeem(type: Type, ticketID: UInt64, receiver: Capability) {
347
+ pre {
348
+ receiver.address == self.redeemer: "receiver must match the redeemer of this shelf"
349
+ self.bins.containsKey(type.identifier): "no bin for provided type"
350
+ }
351
+
352
+ let borrowedBin = self.borrowBin(type: type)!
353
+ let ticket <- borrowedBin.withdrawTicket(ticketID: ticketID)
354
+ let uuid = ticket.uuid
355
+ ticket.withdraw(receiver: receiver)
356
+ let refundCap = ticket.flowTokenRepayment
357
+
358
+ if refundCap != nil && refundCap!.check() && LostAndFound.storageFees[uuid] != nil {
359
+ let refundProvider = LostAndFound.getFlowProvider()
360
+ let repaymentVault <- refundProvider.withdraw(amount: LostAndFound.storageFees[uuid]!)
361
+ refundCap!.borrow()!.deposit(from: <-repaymentVault)
362
+ }
363
+ destroy ticket
364
+
365
+ if borrowedBin.getTicketIDs().length == 0 {
366
+ let bin <- self.bins.remove(key: type.identifier)!
367
+ let uuid = bin.uuid
368
+
369
+ let flowTokenRepayment = bin.flowTokenRepayment
370
+ emit BinDestroyed(redeemer: self.redeemer, type: type)
371
+ let provider = LostAndFound.getFlowProvider()
372
+
373
+ if flowTokenRepayment != nil && LostAndFound.storageFees[uuid] != nil {
374
+ let vault <- provider.withdraw(amount: LostAndFound.storageFees[uuid]!)
375
+ flowTokenRepayment!.borrow()!.deposit(from: <-vault)
376
+ }
377
+ destroy bin
378
+ }
379
+ }
380
+
381
+ destroy () {
382
+ destroy <- self.bins
383
+ LostAndFound.storageFees.remove(key: self.uuid)
384
+ }
385
+ }
386
+
387
+ access(contract) fun getFlowProvider(): &FlowToken.Vault{FungibleToken.Provider} {
388
+ return self.account.borrow<&FlowToken.Vault{FungibleToken.Provider}>(from: /storage/flowTokenVault)!
389
+ }
390
+
391
+ // ShelfManager is a light-weight wrapper to get our shelves into storage.
392
+ pub resource ShelfManager {
393
+ access(self) let shelves: @{Address: Shelf}
394
+
395
+ init() {
396
+ self.shelves <- {}
397
+ }
398
+
399
+ access(contract) fun ensureShelf(_ addr: Address, flowTokenRepayment: Capability<&FlowToken.Vault{FungibleToken.Receiver}>?): &LostAndFound.Shelf {
400
+ if !self.shelves.containsKey(addr) {
401
+ let storageBefore = LostAndFound.account.storageUsed
402
+ let shelf <- create Shelf(redeemer: addr, flowTokenRepayment: flowTokenRepayment)
403
+ let uuid = shelf.uuid
404
+ let oldValue <- self.shelves.insert(key: addr, <-shelf)
405
+
406
+ LostAndFound.storageFees[uuid] = FeeEstimator.storageUsedToFlowAmount(LostAndFound.account.storageUsed - storageBefore)
407
+ destroy oldValue
408
+ }
409
+
410
+ return (&self.shelves[addr] as &LostAndFound.Shelf?)!
411
+ }
412
+
413
+ pub fun deposit(
414
+ redeemer: Address,
415
+ item: @AnyResource,
416
+ memo: String?,
417
+ display: MetadataViews.Display?,
418
+ storagePayment: &FungibleToken.Vault,
419
+ flowTokenRepayment: Capability<&FlowToken.Vault{FungibleToken.Receiver}>?
420
+ ) : UInt64 {
421
+ pre {
422
+ flowTokenRepayment == nil || flowTokenRepayment!.check(): "flowTokenRepayment is not valid"
423
+ storagePayment.getType() == Type<@FlowToken.Vault>(): "storage payment must be in flow tokens"
424
+ }
425
+ let receiver = LostAndFound.account
426
+ .getCapability<&FlowToken.Vault{FungibleToken.Receiver}>(/public/flowTokenReceiver)
427
+ .borrow()!
428
+
429
+
430
+ let storageBeforeShelf = LostAndFound.account.storageUsed
431
+ let shelf = self.ensureShelf(redeemer, flowTokenRepayment: flowTokenRepayment)
432
+ if LostAndFound.account.storageUsed != storageBeforeShelf && LostAndFound.storageFees[shelf.uuid] != nil {
433
+ receiver.deposit(from: <-storagePayment.withdraw(amount: LostAndFound.storageFees[shelf.uuid]!))
434
+ }
435
+
436
+ let storageBeforeBin = LostAndFound.account.storageUsed
437
+ let bin = shelf.ensureBin(type: item.getType(), flowTokenRepayment: flowTokenRepayment)
438
+ if LostAndFound.account.storageUsed != storageBeforeBin {
439
+ receiver.deposit(from: <-storagePayment.withdraw(amount: LostAndFound.storageFees[bin.uuid]!))
440
+ }
441
+
442
+ let storageBefore = LostAndFound.account.storageUsed
443
+ let ticket <- create Ticket(item: <-item, memo: memo, display: display, redeemer: redeemer, flowTokenRepayment: flowTokenRepayment)
444
+ let uuid = ticket.uuid
445
+ let flowTokenRepayment = ticket.flowTokenRepayment
446
+ shelf.deposit(ticket: <-ticket, flowTokenRepayment: flowTokenRepayment)
447
+ let storageUsedAfter = LostAndFound.account.storageUsed
448
+ let storageFee = FeeEstimator.storageUsedToFlowAmount(storageUsedAfter - storageBefore)
449
+ LostAndFound.storageFees[uuid] = storageFee
450
+
451
+ let storagePaymentVault <- storagePayment.withdraw(amount: storageFee)
452
+ receiver.deposit(from: <-storagePaymentVault)
453
+ return uuid
454
+ }
455
+
456
+ pub fun borrowShelf(redeemer: Address): &LostAndFound.Shelf? {
457
+ return &self.shelves[redeemer] as &LostAndFound.Shelf?
458
+ }
459
+
460
+ // deleteShelf
461
+ //
462
+ // delete a shelf if it has no redeemable types
463
+ pub fun deleteShelf(_ addr: Address) {
464
+ let storageBefore = LostAndFound.account.storageUsed
465
+ assert(self.shelves.containsKey(addr), message: "shelf does not exist")
466
+ let tmp <- self.shelves[addr] <- nil
467
+ let shelf <-! tmp!
468
+
469
+ assert(shelf.getRedeemableTypes().length! == 0, message: "shelf still has redeemable types")
470
+ let flowTokenRepayment = shelf.flowTokenRepayment
471
+ let uuid = shelf.uuid
472
+ if flowTokenRepayment != nil && flowTokenRepayment!.check() && LostAndFound.storageFees[uuid] != nil {
473
+ let provider = LostAndFound.getFlowProvider()
474
+ let vault <- provider.withdraw(amount: LostAndFound.storageFees[uuid]!)
475
+ flowTokenRepayment!.borrow()!.deposit(from: <-vault)
476
+ }
477
+ destroy shelf
478
+ emit ShelfDestroyed(redeemer: addr)
479
+ }
480
+
481
+ destroy () {
482
+ destroy <-self.shelves
483
+ }
484
+ }
485
+
486
+ pub resource interface DepositorPublic {
487
+ pub fun balance(): UFix64
488
+ pub fun addFlowTokens(vault: @FlowToken.Vault)
489
+ }
490
+
491
+ pub resource Depositor: DepositorPublic {
492
+ access(self) let flowTokenVault: @FlowToken.Vault
493
+ pub let flowTokenRepayment: Capability<&FlowToken.Vault{FungibleToken.Receiver}>
494
+ access(self) var lowBalanceThreshold: UFix64?
495
+
496
+ access(self) fun checkForLowBalance(): Bool {
497
+ if self.lowBalanceThreshold != nil &&self.balance() <= self.lowBalanceThreshold! {
498
+ emit DepositorBalanceLow(uuid: self.uuid, threshold: self.lowBalanceThreshold!, balance: self.balance())
499
+ return true
500
+ }
501
+
502
+ return false
503
+ }
504
+
505
+ pub fun setLowBalanceThreshold(threshold: UFix64?) {
506
+ self.lowBalanceThreshold = threshold
507
+ }
508
+
509
+ pub fun getLowBalanceThreshold(): UFix64? {
510
+ return self.lowBalanceThreshold
511
+ }
512
+
513
+ pub fun deposit(
514
+ redeemer: Address,
515
+ item: @AnyResource,
516
+ memo: String?,
517
+ display: MetadataViews.Display?
518
+ ) : UInt64 {
519
+ let receiver = LostAndFound.account
520
+ .getCapability<&FlowToken.Vault{FungibleToken.Receiver}>(/public/flowTokenReceiver)
521
+ .borrow()!
522
+
523
+ let storageBeforeShelf = LostAndFound.account.storageUsed
524
+ let shelfManager = LostAndFound.borrowShelfManager()
525
+ let shelf = shelfManager.ensureShelf(redeemer, flowTokenRepayment: self.flowTokenRepayment)
526
+ if LostAndFound.account.storageUsed != storageBeforeShelf && LostAndFound.storageFees[shelf.uuid] != nil {
527
+ receiver.deposit(from: <-self.withdrawTokens(amount: LostAndFound.storageFees[shelf.uuid]!))
528
+ }
529
+
530
+ let storageBeforeBin = LostAndFound.account.storageUsed
531
+ let bin = shelf.ensureBin(type: item.getType(), flowTokenRepayment: self.flowTokenRepayment)
532
+ if storageBeforeBin != LostAndFound.account.storageUsed {
533
+ receiver.deposit(from: <-self.withdrawTokens(amount: LostAndFound.storageFees[bin.uuid]!))
534
+ }
535
+
536
+ let storageBefore = LostAndFound.account.storageUsed
537
+ let ticket <- create Ticket(item: <-item, memo: memo, display: display, redeemer: redeemer, flowTokenRepayment: self.flowTokenRepayment)
538
+
539
+ let flowTokenRepayment = ticket.flowTokenRepayment
540
+ let uuid = ticket.uuid
541
+ shelf!.deposit(ticket: <-ticket, flowTokenRepayment: flowTokenRepayment)
542
+
543
+ let storageFee = FeeEstimator.storageUsedToFlowAmount(LostAndFound.account.storageUsed - storageBefore)
544
+ LostAndFound.storageFees[uuid] = storageFee
545
+
546
+ let storagePaymentVault <- self.withdrawTokens(amount: storageFee)
547
+
548
+ receiver.deposit(from: <-storagePaymentVault)
549
+ return uuid
550
+ }
551
+
552
+ pub fun trySendResource(
553
+ item: @AnyResource,
554
+ cap: Capability,
555
+ memo: String?,
556
+ display: MetadataViews.Display?
557
+ ) {
558
+
559
+ if cap.check<&{NonFungibleToken.CollectionPublic}>() {
560
+ let nft <- item as! @NonFungibleToken.NFT
561
+ cap.borrow<&{NonFungibleToken.CollectionPublic}>()!.deposit(token: <-nft)
562
+ } else if cap.check<&{NonFungibleToken.Receiver}>() {
563
+ let nft <- item as! @NonFungibleToken.NFT
564
+ cap.borrow<&{NonFungibleToken.Receiver}>()!.deposit(token: <-nft)
565
+ } else if cap.check<&{FungibleToken.Receiver}>() {
566
+ let vault <- item as! @FungibleToken.Vault
567
+ cap.borrow<&{FungibleToken.Receiver}>()!.deposit(from: <-vault)
568
+ } else {
569
+ self.deposit(redeemer: cap.address, item: <-item, memo: memo, display: display)
570
+ }
571
+ }
572
+
573
+ pub fun withdrawTokens(amount: UFix64): @FungibleToken.Vault {
574
+ let tokens <-self.flowTokenVault.withdraw(amount: amount)
575
+ emit DepositorTokensWithdrawn(uuid: self.uuid, tokens: amount, balance: self.flowTokenVault.balance)
576
+ self.checkForLowBalance()
577
+ return <-tokens
578
+ }
579
+
580
+ pub fun addFlowTokens(vault: @FlowToken.Vault) {
581
+ let tokensAdded = vault.balance
582
+ self.flowTokenVault.deposit(from: <-vault)
583
+ emit DepositorTokensAdded(uuid: self.uuid, tokens: tokensAdded, balance: self.flowTokenVault.balance)
584
+ self.checkForLowBalance()
585
+ }
586
+
587
+ pub fun balance(): UFix64 {
588
+ return self.flowTokenVault.balance
589
+ }
590
+
591
+ init(_ flowTokenRepayment: Capability<&FlowToken.Vault{FungibleToken.Receiver}>, lowBalanceThreshold: UFix64?) {
592
+ self.flowTokenRepayment = flowTokenRepayment
593
+
594
+ let vault <- FlowToken.createEmptyVault()
595
+ self.flowTokenVault <- vault as! @FlowToken.Vault
596
+ self.lowBalanceThreshold = lowBalanceThreshold
597
+ }
598
+
599
+ destroy() {
600
+ self.flowTokenRepayment.borrow()!.deposit(from: <-self.flowTokenVault)
601
+ }
602
+ }
603
+
604
+ pub fun createDepositor(_ flowTokenRepayment: Capability<&FlowToken.Vault{FungibleToken.Receiver}>, lowBalanceThreshold: UFix64?): @Depositor {
605
+ let depositor <- create Depositor(flowTokenRepayment, lowBalanceThreshold: lowBalanceThreshold)
606
+ emit DepositorCreated(uuid: depositor.uuid)
607
+ return <- depositor
608
+ }
609
+
610
+ pub fun borrowShelfManager(): &LostAndFound.ShelfManager {
611
+ return self.account.getCapability<&LostAndFound.ShelfManager>(LostAndFound.LostAndFoundPublicPath).borrow()!
612
+ }
613
+
614
+ pub fun borrowAllTicketsByType(addr: Address, type: Type): [&LostAndFound.Ticket] {
615
+ let manager = LostAndFound.borrowShelfManager()
616
+ let shelf = manager.borrowShelf(redeemer: addr)
617
+ if shelf == nil {
618
+ return []
619
+ }
620
+
621
+ let bin = shelf!.borrowBin(type: type)
622
+ if bin == nil {
623
+ return []
624
+ }
625
+
626
+ return bin!.borrowAllTicketsByType()
627
+ }
628
+
629
+ pub fun borrowAllTickets(addr: Address): [&LostAndFound.Ticket] {
630
+ let manager = LostAndFound.borrowShelfManager()
631
+ let shelf = manager.borrowShelf(redeemer: addr)
632
+ if shelf == nil {
633
+ return []
634
+ }
635
+
636
+ let types = shelf!.getRedeemableTypes()
637
+ let allTickets = [] as [&LostAndFound.Ticket]
638
+
639
+ for type in types {
640
+ let tickets = LostAndFound.borrowAllTicketsByType(addr: addr, type: type)
641
+ allTickets.appendAll(tickets)
642
+ }
643
+
644
+ return allTickets
645
+ }
646
+
647
+ pub fun redeemAll(type: Type, max: Int?, receiver: Capability) {
648
+ let manager = LostAndFound.borrowShelfManager()
649
+ let shelf = manager.borrowShelf(redeemer: receiver.address)
650
+ assert(shelf != nil, message: "shelf not found")
651
+
652
+ shelf!.redeemAll(type: type, max: max, receiver: receiver)
653
+ let remainingTypes = shelf!.getRedeemableTypes()
654
+ if remainingTypes.length == 0 {
655
+ manager.deleteShelf(receiver.address)
656
+ }
657
+ }
658
+
659
+ pub fun estimateDeposit(
660
+ redeemer: Address,
661
+ item: @AnyResource,
662
+ memo: String?,
663
+ display: MetadataViews.Display?
664
+ ): @DepositEstimate {
665
+ // is there already a shelf?
666
+ let manager = LostAndFound.borrowShelfManager()
667
+ let shelf = manager.borrowShelf(redeemer: redeemer)
668
+ var shelfFee = 0.0
669
+ var binFee = 0.0
670
+ if shelf == nil {
671
+ shelfFee = 0.00001
672
+ binFee = 0.00001
673
+ } else {
674
+ let bin = shelf!.borrowBin(type: item.getType())
675
+ if bin == nil {
676
+ binFee = 0.00001
677
+ }
678
+ }
679
+
680
+ let ftReceiver = LostAndFound.account.getCapability<&FlowToken.Vault{FungibleToken.Receiver}>(/public/flowTokenReceiver)
681
+ let ticket <- create LostAndFound.Ticket(item: <-item, memo: memo, display: display, redeemer: redeemer, flowTokenRepayment: ftReceiver)
682
+ let tmpEstimate <- FeeEstimator.estimateDeposit(item: <-ticket)
683
+ let tmpItem <- tmpEstimate.withdraw() as! @LostAndFound.Ticket
684
+ let item <- tmpItem.takeItem()
685
+ destroy tmpItem
686
+
687
+ let estimate <- create DepositEstimate(item: <-item, storageFee: tmpEstimate.storageFee + shelfFee + binFee)
688
+ destroy tmpEstimate
689
+ return <- estimate
690
+ }
691
+
692
+ pub fun getRedeemableTypes(_ addr: Address): [Type] {
693
+ let manager = LostAndFound.borrowShelfManager()
694
+ let shelf = manager.borrowShelf(redeemer: addr)
695
+ if shelf == nil {
696
+ return []
697
+ }
698
+
699
+ return shelf!.getRedeemableTypes()
700
+ }
701
+
702
+ pub fun deposit(
703
+ redeemer: Address,
704
+ item: @AnyResource,
705
+ memo: String?,
706
+ display: MetadataViews.Display?,
707
+ storagePayment: &FungibleToken.Vault,
708
+ flowTokenRepayment: Capability<&FlowToken.Vault{FungibleToken.Receiver}>?
709
+ ) : UInt64 {
710
+ pre {
711
+ flowTokenRepayment == nil || flowTokenRepayment!.check(): "flowTokenRepayment is not valid"
712
+ storagePayment.getType() == Type<@FlowToken.Vault>(): "storage payment must be in flow tokens"
713
+ }
714
+
715
+ let shelfManager = LostAndFound.borrowShelfManager()
716
+ return shelfManager.deposit(redeemer: redeemer, item: <-item, memo: memo, display: display, storagePayment: storagePayment, flowTokenRepayment: flowTokenRepayment)
717
+ }
718
+
719
+ pub fun trySendResource(
720
+ resource: @AnyResource,
721
+ cap: Capability,
722
+ memo: String?,
723
+ display: MetadataViews.Display?,
724
+ storagePayment: &FungibleToken.Vault,
725
+ flowTokenRepayment: Capability<&FlowToken.Vault{FungibleToken.Receiver}>
726
+ ) {
727
+ if cap.check<&{NonFungibleToken.CollectionPublic}>() {
728
+ let nft <- resource as! @NonFungibleToken.NFT
729
+ cap.borrow<&{NonFungibleToken.CollectionPublic}>()!.deposit(token: <-nft)
730
+ } else if cap.check<&{NonFungibleToken.Receiver}>() {
731
+ let nft <- resource as! @NonFungibleToken.NFT
732
+ cap.borrow<&{NonFungibleToken.Receiver}>()!.deposit(token: <-nft)
733
+ } else if cap.check<&{FungibleToken.Receiver}>() {
734
+ let vault <- resource as! @FungibleToken.Vault
735
+ cap.borrow<&{FungibleToken.Receiver}>()!.deposit(from: <-vault)
736
+ } else {
737
+ LostAndFound.deposit(redeemer: cap.address, item: <-resource, memo: memo, display: display, storagePayment: storagePayment, flowTokenRepayment: flowTokenRepayment)
738
+ }
739
+ }
740
+
741
+ pub fun getAddress(): Address {
742
+ return self.account.address
743
+ }
744
+
745
+ init() {
746
+ self.storageFees = {}
747
+
748
+ self.LostAndFoundPublicPath = /public/lostAndFound
749
+ self.LostAndFoundStoragePath = /storage/lostAndFound
750
+ self.DepositorPublicPath = /public/lostAndFoundDepositor
751
+ self.DepositorStoragePath = /storage/lostAndFoundDepositor
752
+
753
+ let manager <- create ShelfManager()
754
+ self.account.save(<-manager, to: self.LostAndFoundStoragePath)
755
+ self.account.link<&LostAndFound.ShelfManager>(self.LostAndFoundPublicPath, target: self.LostAndFoundStoragePath)
756
+ }
757
+ }
758
+