Smart Contract

StrategyExecutor

A.79f5b5b0f95a160b.StrategyExecutor

Valid From

129,761,539

Deployed

1w ago
Feb 21, 2026, 12:59:19 PM UTC

Dependents

0 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import stFlowToken from 0xd6f80565193ad727
4import VaultCore from 0x79f5b5b0f95a160b
5import LiquidStaking from 0xd6f80565193ad727
6import LendingInterfaces from 0x2df970b6cdee5735
7import LendingComptroller from 0xf80cb737bfe7c792
8import LendingConfig from 0x2df970b6cdee5735
9import SwapRouter from 0xa6850776a94e6551
10import SwapInterfaces from 0xb78ef7afa52ff906
11
12/// Non-custodial strategy executor
13/// Executes strategies directly on vault funds without withdrawal
14access(all) contract StrategyExecutor {
15    
16    access(all) event StrategyExecuted(strategy: String, inputAmount: UFix64, outputAmount: UFix64)
17    access(all) event YieldHarvested(strategy: String, amount: UFix64)
18    
19    // Execute staking strategy on vault funds
20    access(all) fun executeStaking(vaultRef: &VaultCore.Vault, amount: UFix64) {
21        // Vault withdraws for strategy (internal operation)
22        let flowVault <- vaultRef.withdrawForStrategy(
23            assetType: VaultCore.AssetType.flow,
24            amount: amount
25        ) as! @FlowToken.Vault
26        
27        // Stake FLOW -> stFLOW
28        let stFlowResult <- LiquidStaking.stake(flowVault: <-flowVault)
29        let resultAmount = stFlowResult.balance
30        
31        // Return stFLOW directly to vault
32        vaultRef.depositFromStrategy(
33            assetType: VaultCore.AssetType.stflow,
34            from: <-stFlowResult
35        )
36        
37        // Record yield
38        vaultRef.recordYieldHarvest(
39            assetType: VaultCore.AssetType.stflow,
40            amount: 0.0
41        )
42        
43        emit StrategyExecuted(strategy: "Staking", inputAmount: amount, outputAmount: resultAmount)
44    }
45    
46    // Execute looping strategy on vault funds
47    access(all) fun executeLooping(vaultRef: &VaultCore.Vault, amount: UFix64, loops: UInt8) {
48        pre {
49            loops >= 1 && loops <= 3: "Loops must be 1-3"
50        }
51        
52        // Get user certificate for lending
53        let certificate <- LendingComptroller.IssueUserCertificate()
54        
55        // Withdraw initial FLOW from vault
56        var flowVault <- vaultRef.withdrawForStrategy(
57            assetType: VaultCore.AssetType.flow,
58            amount: amount
59        ) as! @FlowToken.Vault
60        
61        // Get pool references
62        let stFlowPool = getAccount(0x44fe3d9157770b2d).capabilities
63            .borrow<&{LendingInterfaces.PoolPublic}>(LendingConfig.PoolPublicPublicPath)
64            ?? panic("Cannot access stFlow pool")
65        
66        let flowPool = getAccount(0x7492e2f9b4acea9a).capabilities
67            .borrow<&{LendingInterfaces.PoolPublic}>(LendingConfig.PoolPublicPublicPath)
68            ?? panic("Cannot access FLOW pool")
69        
70        var totalStFlow: UFix64 = 0.0
71        var currentLoop: UInt8 = 0
72        
73        // Execute loops
74        while currentLoop < loops {
75            let currentBalance = flowVault.balance
76            
77            if currentBalance == 0.0 {
78                break
79            }
80            
81            // Withdraw all FLOW for this loop iteration
82            let flowForThisLoop <- flowVault.withdraw(amount: currentBalance) as! @FlowToken.Vault
83            let flowAmount = flowForThisLoop.balance
84            
85            // Stake FLOW -> stFLOW
86            let stFlowVault <- LiquidStaking.stake(flowVault: <-flowForThisLoop)
87            totalStFlow = totalStFlow + stFlowVault.balance
88            
89            // Supply stFLOW as collateral
90            stFlowPool.supply(
91                supplierAddr: certificate.owner!.address,
92                inUnderlyingVault: <-stFlowVault
93            )
94            
95            currentLoop = currentLoop + 1
96            
97            // If not the last loop, borrow more FLOW
98            if currentLoop < loops {
99                let borrowAmount = flowAmount * 0.7
100                
101                if borrowAmount > 0.0 {
102                    let borrowedFlow <- flowPool.borrow(
103                        userCertificate: &certificate as &{LendingInterfaces.IdentityCertificate},
104                        borrowAmount: borrowAmount
105                    ) as! @FlowToken.Vault
106                    
107                    // Deposit borrowed FLOW back into our working vault
108                    flowVault.deposit(from: <-borrowedFlow)
109                }
110            }
111        }
112        
113        // Clean up
114        destroy flowVault
115        destroy certificate
116        
117        // Record the leveraged position in vault
118        vaultRef.recordYieldHarvest(
119            assetType: VaultCore.AssetType.stflow,
120            amount: totalStFlow
121        )
122        
123        emit StrategyExecuted(strategy: "Looping", inputAmount: amount, outputAmount: totalStFlow)
124    }
125    
126    // Execute optimal swap strategy on vault funds
127    access(all) fun executeSwap(vaultRef: &VaultCore.Vault, amount: UFix64, fromAsset: VaultCore.AssetType, toAsset: VaultCore.AssetType) {
128        // Withdraw from vault for swap
129        let inputVault <- vaultRef.withdrawForStrategy(
130            assetType: fromAsset,
131            amount: amount
132        )
133        
134        var outputVault: @{FungibleToken.Vault}? <- nil
135        
136        if fromAsset == VaultCore.AssetType.flow && toAsset == VaultCore.AssetType.stflow {
137            // FLOW -> stFLOW optimal routing
138            let flowVault <- inputVault as! @FlowToken.Vault
139            let result <- self.optimalFlowToStFlow(flowVault: <-flowVault)
140            outputVault <-! result
141            
142        } else if fromAsset == VaultCore.AssetType.stflow && toAsset == VaultCore.AssetType.flow {
143            // stFLOW -> FLOW optimal routing
144            let stFlowVault <- inputVault as! @stFlowToken.Vault
145            let result <- self.optimalStFlowToFlow(stFlowVault: <-stFlowVault)
146            outputVault <-! result
147            
148        } else {
149            destroy inputVault
150            panic("Unsupported swap pair")
151        }
152        
153        let outputAmount = outputVault?.balance ?? 0.0
154        
155        // Return swapped funds to vault
156        vaultRef.depositFromStrategy(
157            assetType: toAsset,
158            from: <-outputVault!
159        )
160        
161        emit StrategyExecuted(strategy: "Swap", inputAmount: amount, outputAmount: outputAmount)
162    }
163    
164    // Optimal FLOW -> stFLOW routing
165    access(self) fun optimalFlowToStFlow(flowVault: @FlowToken.Vault): @stFlowToken.Vault {
166        let amount = flowVault.balance
167        
168        // Check staking rate
169        let stakingRate = LiquidStaking.calcStFlowFromFlow(flowAmount: amount)
170        
171        // Check DEX rates
172        var bestDexRate: UFix64 = 0.0
173        var bestDex: String = ""
174        
175        let pairV1 = getAccount(0x396c0cda3302d8c5).capabilities
176            .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
177        if pairV1 != nil {
178            let v1Rate = pairV1!.getAmountOut(
179                amountIn: amount,
180                tokenInKey: "A.1654653399040a61.FlowToken"
181            )
182            if v1Rate > bestDexRate {
183                bestDexRate = v1Rate
184                bestDex = "V1"
185            }
186        }
187        
188        let pairStable = getAccount(0xc353b9d685ec427d).capabilities
189            .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
190        if pairStable != nil {
191            let stableRate = pairStable!.getAmountOut(
192                amountIn: amount,
193                tokenInKey: "A.1654653399040a61.FlowToken"
194            )
195            if stableRate > bestDexRate {
196                bestDexRate = stableRate
197                bestDex = "Stable"
198            }
199        }
200        
201        // Execute via best route
202        if stakingRate >= bestDexRate {
203            return <- LiquidStaking.stake(flowVault: <-flowVault)
204        } else if bestDex == "V1" {
205            return <- pairV1!.swap(vaultIn: <-flowVault, exactAmountOut: nil) as! @stFlowToken.Vault
206        } else {
207            return <- pairStable!.swap(vaultIn: <-flowVault, exactAmountOut: nil) as! @stFlowToken.Vault
208        }
209    }
210    
211    // Optimal stFLOW -> FLOW routing
212    access(self) fun optimalStFlowToFlow(stFlowVault: @stFlowToken.Vault): @FlowToken.Vault {
213        let amount = stFlowVault.balance
214        
215        // Check DEX rates (unstaking has delay, so DEX is preferred)
216        var bestRate: UFix64 = 0.0
217        var bestPair: &{SwapInterfaces.PairPublic}? = nil
218        
219        let pairV1 = getAccount(0x396c0cda3302d8c5).capabilities
220            .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
221        if pairV1 != nil {
222            let v1Rate = pairV1!.getAmountOut(
223                amountIn: amount,
224                tokenInKey: "A.d6f80565193ad727.stFlowToken"
225            )
226            if v1Rate > bestRate {
227                bestRate = v1Rate
228                bestPair = pairV1
229            }
230        }
231        
232        let pairStable = getAccount(0xc353b9d685ec427d).capabilities
233            .borrow<&{SwapInterfaces.PairPublic}>(/public/increment_swap_pair)
234        if pairStable != nil {
235            let stableRate = pairStable!.getAmountOut(
236                amountIn: amount,
237                tokenInKey: "A.d6f80565193ad727.stFlowToken"
238            )
239            if stableRate > bestRate {
240                bestRate = stableRate
241                bestPair = pairStable
242            }
243        }
244        
245        if bestPair != nil {
246            return <- bestPair!.swap(vaultIn: <-stFlowVault, exactAmountOut: nil) as! @FlowToken.Vault
247        } else {
248            // Fallback to SwapRouter
249            let deadline = getCurrentBlock().timestamp + 300.0
250            return <- SwapRouter.swapExactTokensForTokens(
251                exactVaultIn: <-stFlowVault,
252                amountOutMin: 0.0,
253                tokenKeyPath: [
254                    "A.d6f80565193ad727.stFlowToken",
255                    "A.1654653399040a61.FlowToken"
256                ],
257                deadline: deadline
258            ) as! @FlowToken.Vault
259        }
260    }
261    
262    // Harvest yields from strategies back to vault
263    access(all) fun harvestYields(vaultRef: &VaultCore.Vault, strategyType: String) {
264        var harvestedAmount: UFix64 = 0.0
265        
266        switch strategyType {
267            case "Lending":
268                harvestedAmount = 0.0
269                
270            case "Farming":
271                harvestedAmount = 0.0
272                
273            default:
274                log("Unknown strategy type")
275        }
276        
277        if harvestedAmount > 0.0 {
278            vaultRef.recordYieldHarvest(
279                assetType: VaultCore.AssetType.flow,
280                amount: harvestedAmount
281            )
282            emit YieldHarvested(strategy: strategyType, amount: harvestedAmount)
283        }
284    }
285}