Smart Contract

FlowtyUtils

A.5c57f79c6694797f.FlowtyUtils

Deployed

2d ago
Feb 25, 2026, 11:48:26 PM UTC

Dependents

0 imports
1import FUSD from 0x3c5959b568896393
2import NonFungibleToken from 0x1d7e57aa55817448
3import FungibleToken from 0xf233dcee88fe0abe
4import LostAndFound from 0x473d6a2c37eab5be
5import MetadataViews from 0x1d7e57aa55817448
6import FlowToken from 0x1654653399040a61
7import NFTCatalog from 0x49a7cda3a1eecc29
8
9pub contract FlowtyUtils {
10    access(contract) var Attributes: {String: AnyStruct}
11
12    pub let FlowtyUtilsStoragePath: StoragePath
13
14    pub resource FlowtyUtilsAdmin {
15        // addSupportedTokenType
16        // add a supported token type that can be used in Flowty loans
17        pub fun addSupportedTokenType(type: Type) {
18            var supportedTokens = FlowtyUtils.Attributes["supportedTokens"]
19            if supportedTokens == nil {
20                supportedTokens = [Type<@FUSD.Vault>()] as! [Type] 
21            }
22
23            let tokens = supportedTokens! as! [Type]
24
25            if !FlowtyUtils.isTokenSupported(type: type) {
26                tokens.append(type)
27            }
28
29            FlowtyUtils.Attributes["supportedTokens"] = tokens
30        }
31
32        pub fun removeSupportedToken(type: Type) {
33            var supportedTokens = FlowtyUtils.Attributes["supportedTokens"]
34            if supportedTokens == nil {
35                supportedTokens = [Type<@FUSD.Vault>()] as! [Type] 
36            }
37
38            let tokens = supportedTokens! as! [Type]
39
40            var index: Int? = nil
41            for idx, t in tokens {
42                if t == type {
43                    index = idx
44                }
45            }
46
47            if let idx = index {
48                tokens.remove(at: idx)
49            }
50
51            FlowtyUtils.Attributes["supportedTokens"] = tokens
52        }
53
54        pub fun setBalancePath(key: String, path: PublicPath): Bool {
55            if FlowtyUtils.Attributes["balancePaths"] == nil {
56                FlowtyUtils.Attributes["balancePaths"] = BalancePaths()
57            }
58
59            return (FlowtyUtils.Attributes["balancePaths"]! as! BalancePaths).set(key: key, path: path)
60        }
61
62        pub fun setRoyaltyOverride(key: String, value: Bool) {
63            if FlowtyUtils.Attributes["royaltyOverrides"] == nil {
64                FlowtyUtils.Attributes["royaltyOverrides"] = {} as {String: Bool}
65            }
66
67            var overrides = FlowtyUtils.Attributes["royaltyOverrides"]! as! {String: Bool}
68            overrides!.insert(key: key, value)
69            FlowtyUtils.Attributes["royaltyOverrides"] = overrides!
70        }
71    }
72
73    pub fun getRoyaltyOverrides(): {String: Bool}? {
74        return FlowtyUtils.Attributes["royaltyOverrides"]! as? {String: Bool}
75    }
76
77    pub fun getRoyaltyOverride(_ t: Type): Bool {
78        var overrides = FlowtyUtils.Attributes["royaltyOverrides"]
79        if overrides == nil {
80            return false
81        }
82
83        let converted = overrides! as! {String: Bool}
84        return converted[t.identifier] != nil ? converted[t.identifier]! : false
85    }
86
87    pub fun isSupported(_ nft: &NonFungibleToken.NFT): Bool {
88        let collections = NFTCatalog.getCollectionsForType(nftTypeIdentifier: nft.getType().identifier)
89        if collections == nil {
90            return false
91        }
92
93        for v in collections!.values {
94            if v {
95                return true
96            }
97        }
98
99        return false
100    }
101
102    pub fun getRoyaltyRate(_ nft: &NonFungibleToken.NFT): UFix64 {
103        // check for overrides first
104        if FlowtyUtils.getRoyaltyOverride(nft.getType()) {
105            return 0.0
106        }
107
108        let royalties = nft.resolveView(Type<MetadataViews.Royalties>()) as! MetadataViews.Royalties?
109        if royalties == nil {
110            return 0.0
111        }
112
113        // count the royalty rate now, then we'll pick them all up after the fact when a loan is settled?
114        var total = 0.0
115        for r in royalties!.getRoyalties() {
116            total = total + r.cut
117        }
118
119        return total
120    }
121
122    pub fun distributeRoyalties(v: @FungibleToken.Vault, cuts: [MetadataViews.Royalty], path: PublicPath) {
123        let balance = v.balance
124        if balance == 0.0 {
125            destroy v
126            return
127        }
128
129        var total = 0.0
130        for c in cuts {
131            total = total + c.cut
132        }
133
134        for i, r in cuts {
135            let amount =  balance * (r.cut/total)
136            let royaltyReceiverCap = getAccount(r.receiver.address).getCapability<&AnyResource{FungibleToken.Receiver}>(path)
137
138            if v.balance < amount || i == cuts.length - 1 {
139                FlowtyUtils.trySendFungibleTokenVault(vault: <-v, receiver: royaltyReceiverCap)
140                return      
141            }
142
143            let cut <- v.withdraw(amount: amount)
144            FlowtyUtils.trySendFungibleTokenVault(vault: <-cut, receiver: royaltyReceiverCap)
145        }
146
147        // this line shouldn't ever be reached but it's best to be safe
148        destroy v
149    }
150
151    pub fun getSupportedTokens(): AnyStruct {
152        return self.Attributes["supportedTokens"]!
153    }
154
155    // getAllowedTokens
156    // return an array of types that are able to be used as the payment type
157    // for loans
158    pub fun getAllowedTokens(): [Type] {
159        var supportedTokens = self.Attributes["supportedTokens"]
160        return supportedTokens != nil ? supportedTokens! as! [Type] : [Type<@FUSD.Vault>()]
161    }
162
163    // isTokenSupported
164    // check if the given type is able to be used as payment
165    pub fun isTokenSupported(type: Type): Bool {
166        for t in FlowtyUtils.getAllowedTokens() {
167            if t == type {
168                return true
169            }
170        }
171
172        return false
173    }
174
175    access(account) fun depositToLostAndFound(
176        redeemer: Address,
177        item: @AnyResource,
178        memo: String?,
179        display: MetadataViews.Display?
180    ) {
181        let depositor = FlowtyUtils.account.borrow<&LostAndFound.Depositor>(from: LostAndFound.DepositorStoragePath)
182        if depositor == nil {
183            let depositEstimate <- LostAndFound.estimateDeposit(redeemer: redeemer, item: <- item, memo: memo, display: display)
184
185            let flowtyFlowVault = self.account.borrow<&FlowToken.Vault{FungibleToken.Provider}>(from: /storage/flowTokenVault)
186            assert(flowtyFlowVault != nil, message: "FlowToken vault is not set up")
187            let storagePaymentVault <- flowtyFlowVault!.withdraw(amount: depositEstimate.storageFee * 1.05)
188
189            let item <- depositEstimate.withdraw()
190            destroy depositEstimate
191
192            let flowtyFlowReceiver = self.account.getCapability<&FlowToken.Vault{FungibleToken.Receiver}>(/public/flowTokenReceiver)
193
194            LostAndFound.deposit(
195                redeemer: redeemer,
196                item: <- item,
197                memo: memo,
198                display: display,
199                storagePayment: &storagePaymentVault as &FungibleToken.Vault,
200                flowTokenRepayment: flowtyFlowReceiver
201            )
202
203            flowtyFlowReceiver.borrow()!.deposit(from: <-storagePaymentVault)   
204            return
205        }
206
207        depositor!.deposit(redeemer: redeemer, item: <-item, memo: memo, display: display)
208
209    }
210
211    access(account) fun trySendFungibleTokenVault(vault: @FungibleToken.Vault, receiver: Capability<&{FungibleToken.Receiver}>){
212        if !receiver.check() {
213            self.depositToLostAndFound(
214                redeemer: receiver.address,
215                item: <- vault,
216                memo: nil,
217                display: nil,
218            )
219        } else {
220            receiver.borrow()!.deposit(from: <-vault)
221        }
222    }
223
224    access(account) fun trySendNFT(nft: @NonFungibleToken.NFT, receiver: Capability<&{NonFungibleToken.CollectionPublic}>) {
225        if !receiver.check() {
226            self.depositToLostAndFound(
227                redeemer: receiver.address,
228                item: <- nft,
229                memo: nil,
230                display: nil,
231            )
232        } else {
233            receiver.borrow()!.deposit(token: <-nft)
234        }
235    }
236
237    pub struct BalancePaths {
238        access(self) var paths: {String: PublicPath}
239
240        access(account) fun get(key: String): PublicPath? {
241            return self.paths[key]
242        }
243        
244
245        access(account) fun set(key: String, path: PublicPath): Bool {
246            let pathOverwritten = self.paths[key] != nil
247
248            self.paths[key] = path
249
250            return pathOverwritten
251        }
252
253        init() {
254            self.paths = {}
255        }
256    }
257
258    pub fun balancePaths(): BalancePaths {
259        if self.Attributes["balancePaths"] == nil {
260            self.Attributes["balancePaths"] = BalancePaths()
261        }
262
263        return self.Attributes["balancePaths"]! as! BalancePaths
264    }
265
266    pub fun getTokenBalance(address: Address, vaultType: Type): UFix64 {
267        // get the account for the address we want the balance for
268        let user = getAccount(address)
269
270        // get the balance path for the user for the given fungible token
271        let balancePath = self.balancePaths().get(key: vaultType.identifier)
272
273        assert(balancePath != nil, message: "No balance path configured for ".concat(vaultType.identifier))
274        
275        // get the FungibleToken.Balance capability located at the path
276        let vaultCap = user.getCapability<&{FungibleToken.Balance}>(balancePath!)
277        
278        // check the capability exists
279        if !vaultCap.check() {
280            return 0.0
281        }
282
283        // borrow the reference
284        let vaultRef = vaultCap.borrow()
285
286        // get the balance of the account
287        return vaultRef?.balance ?? 0.0
288    }
289
290
291    init() {
292        self.Attributes = {}
293
294        self.FlowtyUtilsStoragePath = /storage/FlowtyUtils
295
296        let utilsAdmin <- create FlowtyUtilsAdmin()
297        self.account.save(<-utilsAdmin, to: self.FlowtyUtilsStoragePath)
298    }
299}
300