Smart Contract
FindLostAndFoundWrapper
A.097bafa4e0b48eef.FindLostAndFoundWrapper
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import NonFungibleToken from 0x1d7e57aa55817448
4import MetadataViews from 0x1d7e57aa55817448
5import ViewResolver from 0x1d7e57aa55817448
6import LostAndFound from 0x473d6a2c37eab5be
7import LostAndFoundHelper from 0x473d6a2c37eab5be
8import FlowStorageFees from 0xe467b9dd11fa00df
9import FIND from 0x097bafa4e0b48eef
10import Debug from 0x097bafa4e0b48eef
11import FindViews from 0x097bafa4e0b48eef
12
13
14access(all) contract FindLostAndFoundWrapper {
15 // Events
16 access(all) event NFTDeposited(receiver: Address, receiverName: String?, sender: Address?, senderName: String?, type: String, id: UInt64?, uuid: UInt64?, memo: String?, name: String?, description: String?, thumbnail: String?, collectionName: String?, collectionImage: String?)
17 access(all) event UserStorageSubsidized(receiver: Address, receiverName: String?, sender: Address, senderName: String?, forUUID: UInt64, storageFee: UFix64)
18 access(all) event TicketDeposited(receiver: Address, receiverName: String?, sender: Address, senderName: String?, ticketID: UInt64, type: String, id: UInt64, uuid: UInt64?, memo: String?, name: String?, description: String?, thumbnail: String?, collectionName: String?, collectionImage: String?, flowStorageFee: UFix64)
19 access(all) event TicketRedeemed(receiver: Address, receiverName: String?, ticketID: UInt64, type: String)
20 access(all) event TicketRedeemFailed(receiver: Address, receiverName: String?, ticketID: UInt64, type: String, remark: String)
21
22 // check if they have that storage
23 // npm module for NFT catalog, that can init the storage of the users.
24 // List of what you have in lost and found.
25 // a button to init the storage
26
27 // Mapping of vault uuid to vault.
28 // A method to get around passing the "Vault" reference to Lost and Found to ensure it cannot be hacked.
29 // All vaults should be destroyed after deposit function
30 access(all) let storagePaymentVaults : @{UInt64 : {FungibleToken.Vault}}
31
32 // Deposit
33 access(all) fun depositNFT(
34 receiver: Address,
35 collectionPublicPath: PublicPath,
36 item: FindViews.AuthNFTPointer,
37 memo: String?,
38 storagePayment: auth(FungibleToken.Withdraw) &{FungibleToken.Vault},
39 flowTokenRepayment: Capability<&FlowToken.Vault> ,
40 subsidizeReceiverStorage: Bool
41 ) : UInt64? {
42
43 let receiverAddress = receiver
44
45 let sender = item.owner()
46 let senderName = FIND.reverseLookup(sender)
47
48 let display = item.getDisplay()
49 let collectionDisplay = MetadataViews.getNFTCollectionDisplay(item.getViewResolver())
50 let id = item.id
51 let uuid = item.uuid
52 let type = item.getItemType()
53
54 // calculate the required storage and check sufficient balance
55 let senderStorageBeforeSend = getAccount(sender).storage.used
56
57 let item <- item.withdraw()
58
59 let requiredStorage = senderStorageBeforeSend - getAccount(sender).storage.used
60 let receiverAvailableStorage = getAccount(receiverAddress).storage.capacity- getAccount(receiverAddress).storage.used
61
62 // Try to send before using Lost & FIND
63 let receiverCap = getAccount(receiverAddress).capabilities.get<&{NonFungibleToken.Receiver}>(collectionPublicPath)
64 if receiverCap.check() {
65 // If the receiver has sufficient storage, then subsidize it
66 var readyToSend = true
67 if receiverAvailableStorage < requiredStorage {
68 readyToSend = false
69 if subsidizeReceiverStorage {
70 readyToSend = FindLostAndFoundWrapper.subsidizeUserStorage(requiredStorage: requiredStorage, receiverAvailableStorage: receiverAvailableStorage, receiver: receiver, vault: storagePayment, sender: sender, uuid: item.uuid)
71 }
72 }
73
74 if readyToSend {
75 receiverCap.borrow()!.deposit(token: <- item)
76 emit NFTDeposited(receiver: receiverCap.address, receiverName: FIND.reverseLookup(receiverAddress), sender: sender, senderName: senderName, type: type.identifier, id: id, uuid: uuid, memo: memo, name: display.name, description: display.description, thumbnail: display.thumbnail.uri(), collectionName: collectionDisplay?.name, collectionImage: collectionDisplay?.squareImage?.file?.uri())
77 return nil
78 }
79 }
80
81 let collectionPublicCap = getAccount(receiverAddress).capabilities.get<&{NonFungibleToken.Collection}>(collectionPublicPath)
82 if collectionPublicCap.check() {
83 // If the receiver has sufficient storage, then subsidize it
84 var readyToSend = true
85 if receiverAvailableStorage < requiredStorage {
86 readyToSend = false
87 if subsidizeReceiverStorage {
88 readyToSend = FindLostAndFoundWrapper.subsidizeUserStorage(requiredStorage: requiredStorage, receiverAvailableStorage: receiverAvailableStorage, receiver: receiver, vault: storagePayment, sender: sender, uuid: item.uuid)
89 }
90 }
91
92 if readyToSend {
93 collectionPublicCap.borrow()!.deposit(token: <- item)
94 emit NFTDeposited(receiver: receiverAddress, receiverName: FIND.reverseLookup(receiverAddress), sender: sender, senderName: senderName, type: type.identifier, id: id, uuid: uuid, memo: memo, name: display.name, description: display.description, thumbnail: display.thumbnail.uri(), collectionName: collectionDisplay?.name, collectionImage: collectionDisplay?.squareImage?.file?.uri())
95 return nil
96 }
97 }
98
99 // Calculate storage fees required
100 let estimate <- LostAndFound.estimateDeposit(
101 redeemer: receiverAddress,
102 item: <- item,
103 memo: memo,
104 display: display
105 )
106 // we add 0.00005 here just incase it falls below
107 // extra fees will be deposited back to the sender
108 let vault <- storagePayment.withdraw(amount: estimate.storageFee + 0.00005)
109
110 // Put the payment vault in dictionary and get it's reference, just for safety that we don't pass vault ref to other contracts that we do not control
111 let vaultUUID = vault.uuid
112
113 let vaultRef = FindLostAndFoundWrapper.depositVault(<- vault)
114
115 let flowStorageFee = vaultRef.balance
116 let ticketID = LostAndFound.deposit(
117 redeemer: receiverAddress,
118 item: <- estimate.withdraw(),
119 memo: memo,
120 display: display,
121 storagePayment: vaultRef,
122 flowTokenRepayment: flowTokenRepayment
123 )
124 // Destroy the vault after the payment. The vault should be 0 in balance
125 FindLostAndFoundWrapper.destroyVault(vaultUUID, cap: flowTokenRepayment)
126
127 emit TicketDeposited(receiver: receiverAddress, receiverName: FIND.reverseLookup(receiverAddress), sender: sender, senderName: senderName, ticketID: ticketID, type: type.identifier, id: id, uuid: uuid, memo: memo, name: display.name, description: display.description, thumbnail: display.thumbnail.uri(), collectionName: collectionDisplay?.name, collectionImage: collectionDisplay?.squareImage?.file?.uri(), flowStorageFee: flowStorageFee)
128 destroy estimate
129
130 return ticketID
131 }
132
133 // Redeem
134 access(all) fun redeemNFT(type: Type, ticketID: UInt64, receiverAddress: Address, collectionPublicPath: PublicPath) {
135
136 let cap= getAccount(receiverAddress).capabilities.get<&{NonFungibleToken.Collection}>(collectionPublicPath)
137 if !cap.check() {
138 emit TicketRedeemFailed(receiver: receiverAddress, receiverName: FIND.reverseLookup(receiverAddress), ticketID: ticketID, type: type.identifier, remark: "invalid capability")
139 return
140 }
141
142 let shelf = LostAndFound.borrowShelfManager().borrowShelf(redeemer: receiverAddress) ?? panic("No items to redeem for this user: ".concat(receiverAddress.toString()))
143
144 let bin = shelf.borrowBin(type: type) ?? panic("No items to redeem for this user: ".concat(receiverAddress.toString()))
145 let ticket = bin.borrowTicket(id: ticketID) ?? panic("No items to redeem for this user: ".concat(receiverAddress.toString()))
146 let nftID = ticket.getNonFungibleTokenID() ?? panic("The item you are trying to redeem is not an NFT")
147
148 let sender = ticket.getFlowRepaymentAddress()
149 let memo = ticket.memo
150
151 // if receiverCap is valid, pass that in, otherwise pass collectionPublicCap
152 shelf.redeem(type: type, ticketID: ticketID, receiver: cap)
153
154 let viewResolver=cap.borrow()!.borrowNFT(nftID)!
155 let display = MetadataViews.getDisplay(viewResolver)
156 let collectionDisplay= MetadataViews.getNFTCollectionDisplay(viewResolver)
157
158 var senderName : String? = nil
159 if sender != nil {
160 senderName = FIND.reverseLookup(sender!)
161 }
162 emit NFTDeposited(receiver: receiverAddress, receiverName: FIND.reverseLookup(receiverAddress), sender: sender, senderName: senderName, type: type.identifier, id: nftID, uuid: viewResolver.uuid, memo: memo, name: display?.name, description: display?.description, thumbnail: display?.thumbnail?.uri(), collectionName: collectionDisplay?.name, collectionImage: collectionDisplay?.squareImage?.file?.uri())
163 emit TicketRedeemed(receiver: receiverAddress, receiverName: FIND.reverseLookup(receiverAddress), ticketID: ticketID, type: type.identifier)
164
165 }
166
167 // Check
168 access(all) fun getTickets(user: Address, specificType: Type?) : {String : [LostAndFoundHelper.Ticket]} {
169
170 let allTickets : {String : [LostAndFoundHelper.Ticket]} = {}
171
172 let ticketTypes = LostAndFound.getRedeemableTypes(user)
173 for type in ticketTypes {
174 if specificType != nil {
175 if !type.isSubtype(of: specificType!) {
176 continue
177 }
178 }
179
180 let ticketInfo : [LostAndFoundHelper.Ticket] = []
181 let tickets = LostAndFound.borrowAllTicketsByType(addr: user, type: type)
182
183 let shelf = LostAndFound.borrowShelfManager().borrowShelf(redeemer: user)!
184
185 let bin = shelf.borrowBin(type: type)!
186 let ids = bin.getTicketIDs()
187 for id in ids {
188 let ticket = bin.borrowTicket(id: id)!
189 ticketInfo.append(LostAndFoundHelper.Ticket(ticket, id: id))
190 }
191 allTickets[type.identifier] = ticketInfo
192 }
193
194 return allTickets
195 }
196
197 access(all) fun getTicketIDs(user: Address, specificType: Type?) : {String : [UInt64]} {
198
199 let allTickets : {String : [UInt64]} = {}
200
201 let ticketTypes = LostAndFound.getRedeemableTypes(user)
202 for type in ticketTypes {
203 if specificType != nil {
204 if !type.isSubtype(of: specificType!) {
205 continue
206 }
207 }
208
209 let ticketInfo : [UInt64] = []
210 let tickets = LostAndFound.borrowAllTicketsByType(addr: user, type: type)
211
212 let shelf = LostAndFound.borrowShelfManager().borrowShelf(redeemer: user)!
213
214 let bin = shelf.borrowBin(type: type)!
215 ticketInfo.appendAll(bin.getTicketIDs())
216
217 allTickets[type.identifier] = ticketInfo
218 }
219
220 return allTickets
221 }
222
223 // Check for all types that are in Lost and found which are NFTs
224 access(all) fun getSpecificRedeemableTypes(user: Address, specificType: Type?) : [Type] {
225 let allTypes : [Type] = []
226 if specificType != nil {
227 for type in LostAndFound.getRedeemableTypes(user) {
228 if type.isSubtype(of: specificType!) {
229 allTypes.append(type)
230 }
231 }
232 }
233 return allTypes
234 }
235
236 // Helper function
237 access(contract) fun depositVault(_ vault: @{FungibleToken.Vault}) : auth (FungibleToken.Withdraw) &{FungibleToken.Vault} {
238 let uuid = vault.uuid
239 self.storagePaymentVaults[uuid] <-! vault
240 return (&self.storagePaymentVaults[uuid])!
241 }
242
243 access(contract) fun destroyVault(_ uuid: UInt64, cap: Capability<&{FungibleToken.Receiver}>) {
244 let vault <- self.storagePaymentVaults.remove(key: uuid) ?? panic("Invalid vault UUID. UUID: ".concat(uuid.toString()))
245 if vault.balance != nil {
246 let ref = cap.borrow() ?? panic("The flow repayment capability is not valid")
247 ref.deposit(from: <- vault)
248 return
249 }
250 destroy vault
251 }
252
253 access(contract) fun subsidizeUserStorage(requiredStorage: UInt64, receiverAvailableStorage: UInt64, receiver: Address, vault: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}, sender: Address, uuid: UInt64) : Bool {
254 let subsidizeCapacity = requiredStorage - receiverAvailableStorage
255 let subsidizeAmount = FlowStorageFees.storageCapacityToFlow(FlowStorageFees.convertUInt64StorageBytesToUFix64Megabytes(subsidizeCapacity))
256 let flowReceiverCap = getAccount(receiver).capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
257
258 if !flowReceiverCap.check() {
259 return false
260 }
261 let flowReceiver = flowReceiverCap.borrow()!
262 if !flowReceiver.isInstance(Type<@FlowToken.Vault>()){
263 return false
264 }
265 flowReceiver.deposit(from: <- vault.withdraw(amount: subsidizeAmount))
266
267 emit UserStorageSubsidized(receiver: receiver, receiverName: FIND.reverseLookup(receiver), sender: sender, senderName: FIND.reverseLookup(sender), forUUID: uuid, storageFee: subsidizeAmount)
268 return true
269 }
270
271 init() {
272 self.storagePaymentVaults <- {}
273 }
274
275}
276
277