@flowtyio/flow-contracts 0.0.10 → 0.0.14
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.
- package/README.md +17 -11
- package/add.js +44 -4
- package/contracts/FlowStorageFees.cdc +193 -0
- package/contracts/FlowToken.cdc +274 -0
- package/contracts/FungibleTokenMetadataViews.cdc +183 -0
- package/contracts/TokenForwarding.cdc +82 -0
- package/contracts/dapper/DapperUtilityCoin.cdc +199 -0
- package/contracts/dapper/FlowUtilityToken.cdc +199 -0
- package/contracts/lost-and-found/FeeEstimator.cdc +62 -0
- package/contracts/lost-and-found/LostAndFound.cdc +758 -0
- package/contracts/lost-and-found/LostAndFoundHelper.cdc +42 -0
- package/contracts/nft-catalog/NFTCatalog.cdc +422 -0
- package/contracts/nft-catalog/NFTCatalogAdmin.cdc +175 -0
- package/flow.json +129 -13
- package/index.js +2 -2
- package/package.json +1 -1
|
@@ -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
|
+
|