Smart Contract

FindLostAndFoundWrapper

A.097bafa4e0b48eef.FindLostAndFoundWrapper

Deployed

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

Dependents

1 imports
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