Smart Contract
FlowtyUtils
A.5c57f79c6694797f.FlowtyUtils
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