Smart Contract
SwapStrategy
A.79f5b5b0f95a160b.SwapStrategy
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import stFlowToken from 0xd6f80565193ad727
4import SwapRouter from 0xa6850776a94e6551
5import SwapInterfaces from 0xb78ef7afa52ff906
6import LiquidStaking from 0xd6f80565193ad727
7
8/// DEX swap strategy for rebalancing and optimal routing
9/// Supports: SwapRouter, Increment v1/stable pairs, LiquidStaking direct
10access(all) contract SwapStrategy {
11
12 // ====================================================================
13 // CONSTANTS
14 // ====================================================================
15 access(all) let INCREMENT_V1_PAIR: Address
16 access(all) let INCREMENT_STABLE_PAIR: Address
17
18 // ====================================================================
19 // EVENTS
20 // ====================================================================
21 access(all) event Swapped(fromToken: String, toToken: String, amountIn: UFix64, amountOut: UFix64, route: String)
22 access(all) event OptimalRouteFound(route: String, expectedOutput: UFix64)
23
24 // ====================================================================
25 // STATE
26 // ====================================================================
27 access(self) var totalSwaps: UInt64
28 access(self) var totalVolumeByPair: {String: UFix64}
29
30 // ====================================================================
31 // ENUMS
32 // ====================================================================
33 access(all) enum SwapRoute: UInt8 {
34 access(all) case liquidStaking // Direct FLOW -> stFLOW via LiquidStaking
35 access(all) case incrementV1 // Increment v1 DEX
36 access(all) case incrementStable // Increment stable swap
37 access(all) case swapRouter // General SwapRouter
38 }
39
40 // ====================================================================
41 // STRUCTS
42 // ====================================================================
43 access(all) struct RouteQuote {
44 access(all) let route: SwapRoute
45 access(all) let expectedOutput: UFix64
46 access(all) let routeName: String
47
48 init(route: SwapRoute, expectedOutput: UFix64, routeName: String) {
49 self.route = route
50 self.expectedOutput = expectedOutput
51 self.routeName = routeName
52 }
53 }
54
55 // ====================================================================
56 // STRATEGY RESOURCE
57 // ====================================================================
58 access(all) resource Strategy {
59 access(self) let flowVault: @FlowToken.Vault
60 access(self) let stFlowVault: @stFlowToken.Vault
61
62 init() {
63 self.flowVault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) as! @FlowToken.Vault
64 self.stFlowVault <- stFlowToken.createEmptyVault(vaultType: Type<@stFlowToken.Vault>()) as! @stFlowToken.Vault
65 }
66
67 /// Execute swap with optimal route selection (FLOW -> stFLOW)
68 access(all) fun swapFlowToStFlow(from: @FlowToken.Vault): @stFlowToken.Vault {
69 pre {
70 from.balance > 0.0: "Cannot swap zero"
71 }
72
73 let amount = from.balance
74
75 // Find optimal route
76 let bestRoute = self.findOptimalRouteFlowToStFlow(amount: amount)
77
78 emit OptimalRouteFound(route: bestRoute.routeName, expectedOutput: bestRoute.expectedOutput)
79
80 // Execute via best route
81 switch bestRoute.route {
82 case SwapRoute.liquidStaking:
83 let result <- LiquidStaking.stake(flowVault: <-from)
84 emit Swapped(fromToken: "FLOW", toToken: "stFLOW", amountIn: amount, amountOut: result.balance, route: "LiquidStaking")
85 return <- result
86
87 case SwapRoute.incrementV1:
88 let result <- self.swapViaIncrementV1(from: <-from)
89 emit Swapped(fromToken: "FLOW", toToken: "stFLOW", amountIn: amount, amountOut: result.balance, route: "IncrementV1")
90 return <- result
91
92 case SwapRoute.incrementStable:
93 let result <- self.swapViaIncrementStable(from: <-from)
94 emit Swapped(fromToken: "FLOW", toToken: "stFLOW", amountIn: amount, amountOut: result.balance, route: "IncrementStable")
95 return <- result
96
97 case SwapRoute.swapRouter:
98 let result <- self.swapViaRouter(from: <-from)
99 emit Swapped(fromToken: "FLOW", toToken: "stFLOW", amountIn: amount, amountOut: result.balance, route: "SwapRouter")
100 return <- result
101 }
102
103 // Fallback (should never reach)
104 return <- LiquidStaking.stake(flowVault: <-from)
105 }
106
107 /// Execute swap with optimal route selection (stFLOW -> FLOW)
108 access(all) fun swapStFlowToFlow(from: @stFlowToken.Vault): @FlowToken.Vault {
109 pre {
110 from.balance > 0.0: "Cannot swap zero"
111 }
112
113 let amount = from.balance
114
115 // For stFLOW -> FLOW, DEX is usually better than unstaking
116 let bestRoute = self.findOptimalRouteStFlowToFlow(amount: amount)
117
118 emit OptimalRouteFound(route: bestRoute.routeName, expectedOutput: bestRoute.expectedOutput)
119
120 // Execute via best route
121 switch bestRoute.route {
122 case SwapRoute.incrementV1:
123 let result <- self.swapStFlowViaIncrementV1(from: <-from)
124 emit Swapped(fromToken: "stFLOW", toToken: "FLOW", amountIn: amount, amountOut: result.balance, route: "IncrementV1")
125 return <- result
126
127 case SwapRoute.incrementStable:
128 let result <- self.swapStFlowViaIncrementStable(from: <-from)
129 emit Swapped(fromToken: "stFLOW", toToken: "FLOW", amountIn: amount, amountOut: result.balance, route: "IncrementStable")
130 return <- result
131
132 case SwapRoute.swapRouter:
133 let result <- self.swapStFlowViaRouter(from: <-from)
134 emit Swapped(fromToken: "stFLOW", toToken: "FLOW", amountIn: amount, amountOut: result.balance, route: "SwapRouter")
135 return <- result
136
137 default:
138 // Fallback to stable pair
139 let result <- self.swapStFlowViaIncrementStable(from: <-from)
140 emit Swapped(fromToken: "stFLOW", toToken: "FLOW", amountIn: amount, amountOut: result.balance, route: "IncrementStable")
141 return <- result
142 }
143 }
144
145 /// Find optimal route for FLOW -> stFLOW
146 access(all) fun findOptimalRouteFlowToStFlow(amount: UFix64): RouteQuote {
147 var bestQuote = RouteQuote(
148 route: SwapRoute.liquidStaking,
149 expectedOutput: 0.0,
150 routeName: "None"
151 )
152
153 // Option 1: LiquidStaking direct
154 let stakingEstimate = LiquidStaking.calcStFlowFromFlow(flowAmount: amount)
155 bestQuote = RouteQuote(
156 route: SwapRoute.liquidStaking,
157 expectedOutput: stakingEstimate,
158 routeName: "LiquidStaking"
159 )
160
161 // Option 2: Increment v1 pair
162 let pairV1 = getAccount(SwapStrategy.INCREMENT_V1_PAIR)
163 .capabilities
164 .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
165
166 if pairV1 != nil {
167 let v1Estimate = pairV1!.getAmountOut(
168 amountIn: amount,
169 tokenInKey: "A.1654653399040a61.FlowToken"
170 )
171
172 if v1Estimate > bestQuote.expectedOutput {
173 bestQuote = RouteQuote(
174 route: SwapRoute.incrementV1,
175 expectedOutput: v1Estimate,
176 routeName: "IncrementV1"
177 )
178 }
179 }
180
181 // Option 3: Increment stable pair
182 let pairStable = getAccount(SwapStrategy.INCREMENT_STABLE_PAIR)
183 .capabilities
184 .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
185
186 if pairStable != nil {
187 let stableEstimate = pairStable!.getAmountOut(
188 amountIn: amount,
189 tokenInKey: "A.1654653399040a61.FlowToken"
190 )
191
192 if stableEstimate > bestQuote.expectedOutput {
193 bestQuote = RouteQuote(
194 route: SwapRoute.incrementStable,
195 expectedOutput: stableEstimate,
196 routeName: "IncrementStable"
197 )
198 }
199 }
200
201 return bestQuote
202 }
203
204 /// Find optimal route for stFLOW -> FLOW
205 access(all) fun findOptimalRouteStFlowToFlow(amount: UFix64): RouteQuote {
206 var bestQuote = RouteQuote(
207 route: SwapRoute.incrementStable,
208 expectedOutput: 0.0,
209 routeName: "IncrementStable"
210 )
211
212 // Option 1: Increment v1 pair
213 let pairV1 = getAccount(SwapStrategy.INCREMENT_V1_PAIR)
214 .capabilities
215 .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
216
217 if pairV1 != nil {
218 let v1Estimate = pairV1!.getAmountOut(
219 amountIn: amount,
220 tokenInKey: "A.d6f80565193ad727.stFlowToken"
221 )
222
223 bestQuote = RouteQuote(
224 route: SwapRoute.incrementV1,
225 expectedOutput: v1Estimate,
226 routeName: "IncrementV1"
227 )
228 }
229
230 // Option 2: Increment stable pair
231 let pairStable = getAccount(SwapStrategy.INCREMENT_STABLE_PAIR)
232 .capabilities
233 .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
234
235 if pairStable != nil {
236 let stableEstimate = pairStable!.getAmountOut(
237 amountIn: amount,
238 tokenInKey: "A.d6f80565193ad727.stFlowToken"
239 )
240
241 if stableEstimate > bestQuote.expectedOutput {
242 bestQuote = RouteQuote(
243 route: SwapRoute.incrementStable,
244 expectedOutput: stableEstimate,
245 routeName: "IncrementStable"
246 )
247 }
248 }
249
250 return bestQuote
251 }
252
253 // ====================================================================
254 // SWAP EXECUTION FUNCTIONS
255 // ====================================================================
256
257 access(self) fun swapViaIncrementV1(from: @FlowToken.Vault): @stFlowToken.Vault {
258 let pair = getAccount(SwapStrategy.INCREMENT_V1_PAIR)
259 .capabilities
260 .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
261 ?? panic("Cannot access Increment v1 pair")
262
263 let result <- pair.swap(vaultIn: <-from, exactAmountOut: nil)
264
265 SwapStrategy.totalSwaps = SwapStrategy.totalSwaps + 1
266 SwapStrategy.trackVolume(pair: "IncrementV1", amount: result.balance)
267
268 return <- (result as! @stFlowToken.Vault)
269 }
270
271 access(self) fun swapViaIncrementStable(from: @FlowToken.Vault): @stFlowToken.Vault {
272 let pair = getAccount(SwapStrategy.INCREMENT_STABLE_PAIR)
273 .capabilities
274 .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
275 ?? panic("Cannot access Increment stable pair")
276
277 let result <- pair.swap(vaultIn: <-from, exactAmountOut: nil)
278
279 SwapStrategy.totalSwaps = SwapStrategy.totalSwaps + 1
280 SwapStrategy.trackVolume(pair: "IncrementStable", amount: result.balance)
281
282 return <- (result as! @stFlowToken.Vault)
283 }
284
285 access(self) fun swapViaRouter(from: @FlowToken.Vault): @stFlowToken.Vault {
286 let deadline = getCurrentBlock().timestamp + 300.0
287
288 let result <- SwapRouter.swapExactTokensForTokens(
289 exactVaultIn: <-from,
290 amountOutMin: 0.0,
291 tokenKeyPath: [
292 "A.1654653399040a61.FlowToken",
293 "A.d6f80565193ad727.stFlowToken"
294 ],
295 deadline: deadline
296 )
297
298 SwapStrategy.totalSwaps = SwapStrategy.totalSwaps + 1
299 SwapStrategy.trackVolume(pair: "SwapRouter", amount: result.balance)
300
301 return <- (result as! @stFlowToken.Vault)
302 }
303
304 access(self) fun swapStFlowViaIncrementV1(from: @stFlowToken.Vault): @FlowToken.Vault {
305 let pair = getAccount(SwapStrategy.INCREMENT_V1_PAIR)
306 .capabilities
307 .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
308 ?? panic("Cannot access Increment v1 pair")
309
310 let result <- pair.swap(vaultIn: <-from, exactAmountOut: nil)
311
312 SwapStrategy.totalSwaps = SwapStrategy.totalSwaps + 1
313 SwapStrategy.trackVolume(pair: "IncrementV1", amount: result.balance)
314
315 return <- (result as! @FlowToken.Vault)
316 }
317
318 access(self) fun swapStFlowViaIncrementStable(from: @stFlowToken.Vault): @FlowToken.Vault {
319 let pair = getAccount(SwapStrategy.INCREMENT_STABLE_PAIR)
320 .capabilities
321 .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
322 ?? panic("Cannot access Increment stable pair")
323
324 let result <- pair.swap(vaultIn: <-from, exactAmountOut: nil)
325
326 SwapStrategy.totalSwaps = SwapStrategy.totalSwaps + 1
327 SwapStrategy.trackVolume(pair: "IncrementStable", amount: result.balance)
328
329 return <- (result as! @FlowToken.Vault)
330 }
331
332 access(self) fun swapStFlowViaRouter(from: @stFlowToken.Vault): @FlowToken.Vault {
333 let deadline = getCurrentBlock().timestamp + 300.0
334
335 let result <- SwapRouter.swapExactTokensForTokens(
336 exactVaultIn: <-from,
337 amountOutMin: 0.0,
338 tokenKeyPath: [
339 "A.d6f80565193ad727.stFlowToken",
340 "A.1654653399040a61.FlowToken"
341 ],
342 deadline: deadline
343 )
344
345 SwapStrategy.totalSwaps = SwapStrategy.totalSwaps + 1
346 SwapStrategy.trackVolume(pair: "SwapRouter", amount: result.balance)
347
348 return <- (result as! @FlowToken.Vault)
349 }
350
351 /// Harvest - return accumulated tokens
352 access(all) fun harvest(): @{FungibleToken.Vault} {
353 // Return any accumulated FLOW
354 let balance = self.flowVault.balance
355
356 if balance > 0.0 {
357 return <- self.flowVault.withdraw(amount: balance)
358 } else {
359 return <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>())
360 }
361 }
362
363 /// Emergency exit - return all tokens
364 access(all) fun emergencyExit(): @{FungibleToken.Vault} {
365 let flowBalance = self.flowVault.balance
366
367 if flowBalance > 0.0 {
368 return <- self.flowVault.withdraw(amount: flowBalance)
369 } else {
370 return <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>())
371 }
372 }
373
374 access(all) fun getBalances(): {String: UFix64} {
375 return {
376 "flow": self.flowVault.balance,
377 "stflow": self.stFlowVault.balance
378 }
379 }
380 }
381
382 // ====================================================================
383 // CONTRACT FUNCTIONS
384 // ====================================================================
385 access(all) fun createStrategy(): @Strategy {
386 return <- create Strategy()
387 }
388
389 access(contract) fun trackVolume(pair: String, amount: UFix64) {
390 if self.totalVolumeByPair[pair] == nil {
391 self.totalVolumeByPair[pair] = 0.0
392 }
393 self.totalVolumeByPair[pair] = self.totalVolumeByPair[pair]! + amount
394 }
395
396 access(all) fun getMetrics(): {String: AnyStruct} {
397 return {
398 "totalSwaps": self.totalSwaps,
399 "totalVolumeByPair": self.totalVolumeByPair
400 }
401 }
402
403 /// Get swap quote for FLOW -> stFLOW
404 access(all) fun getQuoteFlowToStFlow(amount: UFix64): RouteQuote {
405 // Create temporary strategy to access quote function
406 let tempStrategy <- create Strategy()
407 let quote = tempStrategy.findOptimalRouteFlowToStFlow(amount: amount)
408 destroy tempStrategy
409 return quote
410 }
411
412 /// Get swap quote for stFLOW -> FLOW
413 access(all) fun getQuoteStFlowToFlow(amount: UFix64): RouteQuote {
414 let tempStrategy <- create Strategy()
415 let quote = tempStrategy.findOptimalRouteStFlowToFlow(amount: amount)
416 destroy tempStrategy
417 return quote
418 }
419
420 // ====================================================================
421 // INITIALIZATION
422 // ====================================================================
423 init() {
424 self.INCREMENT_V1_PAIR = 0x396c0cda3302d8c5
425 self.INCREMENT_STABLE_PAIR = 0xc353b9d685ec427d
426
427 self.totalSwaps = 0
428 self.totalVolumeByPair = {}
429 }
430}