Smart Contract

SwapStrategy

A.79f5b5b0f95a160b.SwapStrategy

Valid From

128,833,731

Deployed

1w ago
Feb 20, 2026, 08:41:12 AM UTC

Dependents

0 imports
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}