Smart Contract

FlowVaultsStrategies

A.b1d63873c3cc9f79.FlowVaultsStrategies

Valid From

132,812,267

Deployed

1w ago
Feb 19, 2026, 10:35:24 AM UTC

Dependents

0 imports
1// standards
2import FungibleToken from 0xf233dcee88fe0abe
3import FlowToken from 0x1654653399040a61
4import EVM from 0xe467b9dd11fa00df
5// DeFiActions
6import DeFiActionsUtils from 0x6d888f175c158410
7import DeFiActions from 0x6d888f175c158410
8import SwapConnectors from 0xe1a479f0cb911df9
9import FungibleTokenConnectors from 0x0c237e1265caa7a3
10// amm integration
11import UniswapV3SwapConnectors from 0xa7825d405ac89518
12import ERC4626SwapConnectors from 0x04f5ae6bef48c1fc
13import ERC4626Utils from 0x04f5ae6bef48c1fc
14// Lending protocol
15import FlowALP from 0x6b00ff876c299c61
16// FlowVaults platform
17import FlowVaultsClosedBeta from 0xb1d63873c3cc9f79
18import FlowVaults from 0xb1d63873c3cc9f79
19import FlowVaultsAutoBalancers from 0xb1d63873c3cc9f79
20// tokens
21import YieldToken from 0xb1d63873c3cc9f79
22import MOET from 0x6b00ff876c299c61
23// vm bridge
24import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
25import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
26import FlowEVMBridge from 0x1e4aa0b87d10b141
27// live oracles
28import ERC4626PriceOracles from 0x04f5ae6bef48c1fc
29// mocks
30import MockOracle from 0xb1d63873c3cc9f79
31import MockSwapper from 0xb1d63873c3cc9f79
32
33/// THIS CONTRACT IS A MOCK AND IS NOT INTENDED FOR USE IN PRODUCTION
34/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
35///
36/// FlowVaultsStrategies
37///
38/// This contract defines Strategies used in the FlowVaults platform.
39///
40/// A Strategy instance can be thought of as objects wrapping a stack of DeFiActions connectors wired together to
41/// (optimally) generate some yield on initial deposits. Strategies can be simple such as swapping into a yield-bearing
42/// asset (such as stFLOW) or more complex DeFiActions stacks.
43///
44/// A StrategyComposer is tasked with the creation of a supported Strategy. It's within the stacking of DeFiActions
45/// connectors that the true power of the components lies.
46///
47access(all) contract FlowVaultsStrategies {
48
49    access(all) let univ3FactoryEVMAddress: EVM.EVMAddress
50    access(all) let univ3RouterEVMAddress: EVM.EVMAddress
51    access(all) let univ3QuoterEVMAddress: EVM.EVMAddress
52    access(all) let yieldTokenEVMAddress: EVM.EVMAddress
53
54    /// Canonical StoragePath where the StrategyComposerIssuer should be stored
55    access(all) let IssuerStoragePath: StoragePath
56
57    /// This is the first Strategy implementation, wrapping a FlowALP Position along with its related Sink &
58    /// Source. While this object is a simple wrapper for the top-level collateralized position, the true magic of the
59    /// DeFiActions is in the stacking of the related connectors. This stacking logic can be found in the
60    /// TracerStrategyComposer construct.
61    access(all) resource TracerStrategy : FlowVaults.Strategy, DeFiActions.IdentifiableResource {
62        /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
63        /// specific Identifier to associated connectors on construction
64        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
65        access(self) let position: FlowALP.Position
66        access(self) var sink: {DeFiActions.Sink}
67        access(self) var source: {DeFiActions.Source}
68
69        init(id: DeFiActions.UniqueIdentifier, collateralType: Type, position: FlowALP.Position) {
70            self.uniqueID = id
71            self.position = position
72            self.sink = position.createSink(type: collateralType)
73            self.source = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true)
74        }
75
76        // Inherited from FlowVaults.Strategy default implementation
77        // access(all) view fun isSupportedCollateralType(_ type: Type): Bool
78
79        access(all) view fun getSupportedCollateralTypes(): {Type: Bool} {
80            return { self.sink.getSinkType(): true }
81        }
82        /// Returns the amount available for withdrawal via the inner Source
83        access(all) fun availableBalance(ofToken: Type): UFix64 {
84            return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
85        }
86        /// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
87        access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
88            self.sink.depositCapacity(from: from)
89        }
90        /// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported,
91        /// an empty Vault is returned.
92        access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} {
93            if ofToken != self.source.getSourceType() {
94                return <- DeFiActionsUtils.getEmptyVault(ofToken)
95            }
96            return <- self.source.withdrawAvailable(maxAmount: maxAmount)
97        }
98        /// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer
99        access(contract) fun burnCallback() {
100            FlowVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!)
101        }
102        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
103            return DeFiActions.ComponentInfo(
104                type: self.getType(),
105                id: self.id(),
106                innerComponents: []
107            )
108        }
109        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
110            return self.uniqueID
111        }
112        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
113            self.uniqueID = id
114        }
115    }
116
117    /// This StrategyComposer builds a TracerStrategy
118    access(all) resource TracerStrategyComposer : FlowVaults.StrategyComposer {
119        /// Returns the Types of Strategies composed by this StrategyComposer
120        access(all) view fun getComposedStrategyTypes(): {Type: Bool} {
121            return { Type<@TracerStrategy>(): true }
122        }
123
124        /// Returns the Vault types which can be used to initialize a given Strategy
125        access(all) view fun getSupportedInitializationVaults(forStrategy: Type): {Type: Bool} {
126            return { Type<@FlowToken.Vault>(): true }
127        }
128
129        /// Returns the Vault types which can be deposited to a given Strategy instance if it was initialized with the
130        /// provided Vault type
131        access(all) view fun getSupportedInstanceVaults(forStrategy: Type, initializedWith: Type): {Type: Bool} {
132            return { Type<@FlowToken.Vault>(): true }
133        }
134
135        /// Composes a Strategy of the given type with the provided funds
136        access(all) fun createStrategy(
137            _ type: Type,
138            uniqueID: DeFiActions.UniqueIdentifier,
139            withFunds: @{FungibleToken.Vault}
140        ): @{FlowVaults.Strategy} {
141            // this PriceOracle is mocked and will be shared by all components used in the TracerStrategy
142            // TODO: add ERC4626 price oracle
143            let oracle = MockOracle.PriceOracle()
144
145            // assign token types
146
147            let moetTokenType: Type = Type<@MOET.Vault>()
148            let yieldTokenType = Type<@YieldToken.Vault>()
149            // assign collateral & flow token types
150            let collateralType = withFunds.getType()
151
152            // configure and AutoBalancer for this stack
153            let autoBalancer = FlowVaultsAutoBalancers._initNewAutoBalancer(
154                oracle: oracle,             // used to determine value of deposits & when to rebalance
155                vaultType: yieldTokenType,  // the type of Vault held by the AutoBalancer
156                lowerThreshold: 0.95,       // set AutoBalancer to pull from rebalanceSource when balance is 5% below value of deposits
157                upperThreshold: 1.05,       // set AutoBalancer to push to rebalanceSink when balance is 5% below value of deposits
158                rebalanceSink: nil,         // nil on init - will be set once a PositionSink is available
159                rebalanceSource: nil,       // nil on init - not set for TracerStrategy
160                uniqueID: uniqueID          // identifies AutoBalancer as part of this Strategy
161            )
162            // enables deposits of YieldToken to the AutoBalancer
163            let abaSink = autoBalancer.createBalancerSink() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)")
164            // enables withdrawals of YieldToken from the AutoBalancer
165            let abaSource = autoBalancer.createBalancerSource() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)")
166
167            // init Stable <> YIELD swappers
168            //
169            // Stable -> YieldToken
170            let stableToYieldSwapper = MockSwapper.Swapper(
171                inVault: moetTokenType,
172                outVault: yieldTokenType,
173                uniqueID: uniqueID
174            )
175            // YieldToken -> Stable
176            let yieldToStableSwapper = MockSwapper.Swapper(
177                inVault: yieldTokenType,
178                outVault: moetTokenType,
179                uniqueID: uniqueID
180            )
181
182            // init SwapSink directing swapped funds to AutoBalancer
183            //
184            // Swaps provided Stable to YieldToken & deposits to the AutoBalancer
185            let abaSwapSink = SwapConnectors.SwapSink(swapper: stableToYieldSwapper, sink: abaSink, uniqueID: uniqueID)
186            // Swaps YieldToken & provides swapped Stable, sourcing YieldToken from the AutoBalancer
187            let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToStableSwapper, source: abaSource, uniqueID: uniqueID)
188
189            // open a FlowALP position
190            let poolCap = FlowVaultsStrategies.account.storage.load<Capability<auth(FlowALP.EParticipant, FlowALP.EPosition) &FlowALP.Pool>>(
191                from: FlowALP.PoolCapStoragePath
192            ) ?? panic("Missing pool capability")
193
194            let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap")
195
196            let pid = poolRef.createPosition(
197                funds: <-withFunds,
198                issuanceSink: abaSwapSink,
199                repaymentSource: abaSwapSource,
200                pushToDrawDownSink: true
201            )
202            let position = FlowALP.Position(id: pid, pool: poolCap)
203            FlowVaultsStrategies.account.storage.save(poolCap, to: FlowALP.PoolCapStoragePath)
204
205            // get Sink & Source connectors relating to the new Position
206            let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true)
207            let positionSource = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) // TODO: may need to be false
208
209            // init YieldToken -> FLOW Swapper
210            let yieldToFlowSwapper = MockSwapper.Swapper(
211                inVault: yieldTokenType,
212                outVault: collateralType, 
213                uniqueID: uniqueID
214            )
215            // allows for YieldToken to be deposited to the Position
216            let positionSwapSink = SwapConnectors.SwapSink(swapper: yieldToFlowSwapper, sink: positionSink, uniqueID: uniqueID)
217
218            // set the AutoBalancer's rebalance Sink which it will use to deposit overflown value,
219            // recollateralizing the position
220            autoBalancer.setSink(positionSwapSink, updateSinkID: true)
221
222            return <-create TracerStrategy(
223                id: DeFiActions.createUniqueIdentifier(),
224                collateralType: collateralType,
225                position: position
226            )
227        }
228    }
229
230    /// This strategy uses mUSDC vaults
231    access(all) resource mUSDCStrategy : FlowVaults.Strategy, DeFiActions.IdentifiableResource {
232        /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
233        /// specific Identifier to associated connectors on construction
234        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
235        access(self) let position: FlowALP.Position
236        access(self) var sink: {DeFiActions.Sink}
237        access(self) var source: {DeFiActions.Source}
238
239        init(id: DeFiActions.UniqueIdentifier, collateralType: Type, position: FlowALP.Position) {
240            self.uniqueID = id
241            self.position = position
242            self.sink = position.createSink(type: collateralType)
243            self.source = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true)
244        }
245
246        // Inherited from FlowVaults.Strategy default implementation
247        // access(all) view fun isSupportedCollateralType(_ type: Type): Bool
248
249        access(all) view fun getSupportedCollateralTypes(): {Type: Bool} {
250            return { self.sink.getSinkType(): true }
251        }
252        /// Returns the amount available for withdrawal via the inner Source
253        access(all) fun availableBalance(ofToken: Type): UFix64 {
254            return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0
255        }
256        /// Deposits up to the inner Sink's capacity from the provided authorized Vault reference
257        access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
258            self.sink.depositCapacity(from: from)
259        }
260        /// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported,
261        /// an empty Vault is returned.
262        access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} {
263            if ofToken != self.source.getSourceType() {
264                return <- DeFiActionsUtils.getEmptyVault(ofToken)
265            }
266            return <- self.source.withdrawAvailable(maxAmount: maxAmount)
267        }
268        /// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer
269        access(contract) fun burnCallback() {
270            FlowVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!)
271        }
272        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
273            return DeFiActions.ComponentInfo(
274                type: self.getType(),
275                id: self.id(),
276                innerComponents: [
277                    self.sink.getComponentInfo(),
278                    self.source.getComponentInfo()
279                ]
280            )
281        }
282        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
283            return self.uniqueID
284        }
285        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
286            self.uniqueID = id
287        }
288    }
289
290    /// This StrategyComposer builds a mUSDCStrategy
291    access(all) resource mUSDCStrategyComposer : FlowVaults.StrategyComposer {
292        /// { Strategy Type: { Collateral Type: { String: AnyStruct } } }
293        access(self) let config: {Type: {Type: {String: AnyStruct}}}
294
295        init(_ config: {Type: {Type: {String: AnyStruct}}}) {
296            self.config = config
297        }
298
299        /// Returns the Types of Strategies composed by this StrategyComposer
300        access(all) view fun getComposedStrategyTypes(): {Type: Bool} {
301            let composed: {Type: Bool} = {}
302            for t in self.config.keys {
303                composed[t] = true
304            }
305            return composed
306        }
307
308        /// Returns the Vault types which can be used to initialize a given Strategy
309        access(all) view fun getSupportedInitializationVaults(forStrategy: Type): {Type: Bool} {
310            let supported: {Type: Bool} = {}
311            if let strategyConfig = &self.config[forStrategy] as &{Type: {String: AnyStruct}}? {
312                for collateralType in strategyConfig.keys {
313                    supported[collateralType] = true
314                }
315            }
316            return supported
317        }
318
319        /// Returns the Vault types which can be deposited to a given Strategy instance if it was initialized with the
320        /// provided Vault type
321        access(all) view fun getSupportedInstanceVaults(forStrategy: Type, initializedWith: Type): {Type: Bool} {
322            let supportedInitVaults = self.getSupportedInitializationVaults(forStrategy: forStrategy)
323            if supportedInitVaults[initializedWith] == true {
324                return { initializedWith: true }
325            }
326            return {}
327        }
328
329        /// Composes a Strategy of the given type with the provided funds
330        access(all) fun createStrategy(
331            _ type: Type,
332            uniqueID: DeFiActions.UniqueIdentifier,
333            withFunds: @{FungibleToken.Vault}
334        ): @{FlowVaults.Strategy} {
335            let collateralType = withFunds.getType()
336            let strategyConfig = self.config[type]
337                ?? panic("Could not find a config for Strategy \(type.identifier) initialized with \(collateralType.identifier)")
338            let collateralConfig = strategyConfig[collateralType]
339                ?? panic("Could not find config for collateral \(collateralType.identifier) when creating Strategy \(type.identifier)")
340
341            // assign token types & associated EVM Addresses
342            let moetTokenType: Type = Type<@MOET.Vault>()
343            let moetTokenEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: moetTokenType)
344                ?? panic("Token Vault type \(moetTokenType.identifier) has not yet been registered with the VMbridge")
345            let yieldTokenEVMAddress = collateralConfig["yieldTokenEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"yieldTokenEVMAddress\" in config")
346            let yieldTokenType = FlowEVMBridgeConfig.getTypeAssociated(with: yieldTokenEVMAddress)
347                ?? panic("Could not retrieve the VM Bridge associated Type for the yield token address \(yieldTokenEVMAddress.toString())")
348
349            // assign underlying asset EVM address & type - assumed to be stablecoin for the tracer strategy
350            let underlying4626AssetEVMAddress = ERC4626Utils.underlyingAssetEVMAddress(
351                    vault: yieldTokenEVMAddress
352                ) ?? panic("Could not get the underlying asset's EVM address for ERC4626Vault \(yieldTokenEVMAddress.toString())")
353            let underlying4626AssetType = FlowEVMBridgeConfig.getTypeAssociated(with: underlying4626AssetEVMAddress)
354                ?? panic("Could not retrieve the VM Bridge associated Type for the ERC4626 underlying asset \(underlying4626AssetEVMAddress.toString())")
355
356            // create the oracle for the assets to be held in the AutoBalancer retrieving the NAV of the 4626 vault
357            let yieldTokenOracle = ERC4626PriceOracles.PriceOracle(
358                    vault: yieldTokenEVMAddress,
359                    asset: underlying4626AssetType,
360                    // asset: moetTokenType, // TODO: make a composite oracle that returns the price denominated in MOET
361                    uniqueID: uniqueID
362                )
363
364            // configure and AutoBalancer for this stack
365            let autoBalancer = FlowVaultsAutoBalancers._initNewAutoBalancer(
366                    oracle: yieldTokenOracle,   // used to determine value of deposits & when to rebalance
367                    vaultType: yieldTokenType,  // the type of Vault held by the AutoBalancer
368                    lowerThreshold: 0.95,       // set AutoBalancer to pull from rebalanceSource when balance is 5% below value of deposits
369                    upperThreshold: 1.05,       // set AutoBalancer to push to rebalanceSink when balance is 5% below value of deposits
370                    rebalanceSink: nil,         // nil on init - will be set once a PositionSink is available
371                    rebalanceSource: nil,       // nil on init - not set for TracerStrategy
372                    uniqueID: uniqueID          // identifies AutoBalancer as part of this Strategy
373                )
374            // enables deposits of YieldToken to the AutoBalancer
375            let abaSink = autoBalancer.createBalancerSink() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)")
376            // enables withdrawals of YieldToken from the AutoBalancer
377            let abaSource = autoBalancer.createBalancerSource() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)")
378
379            // create MOET <-> YIELD swappers
380            //
381            // get Uniswap V3 addresses from config
382            let univ3FactoryEVMAddress = collateralConfig["univ3FactoryEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"univ3FactoryEVMAddress\" in config")
383            let univ3RouterEVMAddress = collateralConfig["univ3RouterEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"univ3RouterEVMAddress\" in config")
384            let univ3QuoterEVMAddress = collateralConfig["univ3QuoterEVMAddress"] as? EVM.EVMAddress ?? panic("Could not find \"univ3QuoterEVMAddress\" in config")
385            // MOET -> YIELD - MOET can swap to YieldToken via two primary routes
386            // - via AMM swap pairing MOET <-> YIELD
387            // - via 4626 vault, swapping first to underlying asset then depositing to the 4626 vault
388            // MOET -> YIELD high-level Swapper then contains
389            //     - MultiSwapper aggregates across two sub-swappers
390            //         - MOET -> YIELD (UniV3 Swapper)
391            //         - SequentialSwapper
392            //             - MOET -> UNDERLYING (UniV3 Swapper)
393            //             - UNDERLYING -> YIELD (ERC4626Swapper)
394            let moetToYieldAMMSwapper = UniswapV3SwapConnectors.Swapper(
395                    factoryAddress: univ3FactoryEVMAddress,
396                    routerAddress: univ3RouterEVMAddress,
397                    quoterAddress: univ3QuoterEVMAddress,
398                    tokenPath: [moetTokenEVMAddress, yieldTokenEVMAddress],
399                    feePath: [100],
400                    inVault: moetTokenType,
401                    outVault: yieldTokenType,
402                    coaCapability: FlowVaultsStrategies._getCOACapability(),
403                    uniqueID: uniqueID
404                )
405            // Swap MOET -> UNDERLYING via AMM
406            let moetToUnderlyingAssetSwapper = UniswapV3SwapConnectors.Swapper(
407                    factoryAddress: univ3FactoryEVMAddress,
408                    routerAddress: univ3RouterEVMAddress,
409                    quoterAddress: univ3QuoterEVMAddress,
410                    tokenPath: [moetTokenEVMAddress, underlying4626AssetEVMAddress],
411                    feePath: [100],
412                    inVault: moetTokenType,
413                    outVault: underlying4626AssetType,
414                    coaCapability: FlowVaultsStrategies._getCOACapability(),
415                    uniqueID: uniqueID
416                )
417            // Swap UNDERLYING -> YIELD via ERC4626 Vault
418            let underlyingTo4626Swapper = ERC4626SwapConnectors.Swapper(
419                    asset: underlying4626AssetType,
420                    vault: yieldTokenEVMAddress,
421                    coa: FlowVaultsStrategies._getCOACapability(),
422                    feeSource: FlowVaultsStrategies._createFeeSource(withID: uniqueID),
423                    uniqueID: uniqueID
424                )
425            // Compose v3 swapper & 4626 swapper into sequential swapper for MOET -> UNDERLYING -> YIELD
426            let moetToYieldSeqSwapper = SwapConnectors.SequentialSwapper(
427                    swappers: [moetToUnderlyingAssetSwapper, underlyingTo4626Swapper],
428                    uniqueID: uniqueID
429                )
430            // Finally, add the two MOET -> YIELD swappers into an aggregate MultiSwapper
431            let moetToYieldSwapper = SwapConnectors.MultiSwapper(
432                    inVault: moetTokenType,
433                    outVault: yieldTokenType,
434                    swappers: [moetToYieldAMMSwapper, moetToYieldSeqSwapper],
435                    uniqueID: uniqueID
436                )
437
438            // YIELD -> MOET
439            // - Targets the MOET <-> YIELD pool as the only route since withdraws from the ERC4626 Vault are async
440            let yieldToMOETSwapper = UniswapV3SwapConnectors.Swapper(
441                    factoryAddress: univ3FactoryEVMAddress,
442                    routerAddress: univ3RouterEVMAddress,
443                    quoterAddress: univ3QuoterEVMAddress,
444                    tokenPath: [yieldTokenEVMAddress, moetTokenEVMAddress],
445                    feePath: [100],
446                    inVault: yieldTokenType,
447                    outVault: moetTokenType,
448                    coaCapability: FlowVaultsStrategies._getCOACapability(),
449                    uniqueID: uniqueID
450                )
451
452            // init SwapSink directing swapped funds to AutoBalancer
453            //
454            // Swaps provided MOET to YIELD & deposits to the AutoBalancer
455            let abaSwapSink = SwapConnectors.SwapSink(swapper: moetToYieldSwapper, sink: abaSink, uniqueID: uniqueID)
456            // Swaps YIELD & provides swapped MOET, sourcing YIELD from the AutoBalancer
457            let abaSwapSource = SwapConnectors.SwapSource(swapper: yieldToMOETSwapper, source: abaSource, uniqueID: uniqueID)
458
459            // open a FlowALP position
460            let poolCap = FlowVaultsStrategies.account.storage.copy<Capability<auth(FlowALP.EParticipant, FlowALP.EPosition) &FlowALP.Pool>>(
461                    from: FlowALP.PoolCapStoragePath
462                ) ?? panic("Missing or invalid pool capability")
463            let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap")
464
465            let pid = poolRef.createPosition(
466                    funds: <-withFunds,
467                    issuanceSink: abaSwapSink,
468                    repaymentSource: abaSwapSource,
469                    pushToDrawDownSink: true
470                )
471            let position = FlowALP.Position(id: pid, pool: poolCap)
472
473            // get Sink & Source connectors relating to the new Position
474            let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true)
475            let positionSource = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true)
476
477            // init YieldToken -> FLOW Swapper
478            //
479            // get UniswapV3 path configs
480            let collateralUniV3AddressPathConfig = collateralConfig["yieldToCollateralUniV3AddressPaths"] as? {Type: [EVM.EVMAddress]}
481                ?? panic("Could not find UniswapV3 address paths config when creating Strategy \(type.identifier) with collateral \(collateralType.identifier)")
482            let uniV3AddressPath = collateralUniV3AddressPathConfig[collateralType]
483                ?? panic("Could not find UniswapV3 address path for collateral type \(collateralType.identifier)")
484            assert(uniV3AddressPath.length > 1, message: "Invalid Uniswap V3 swap path length of \(uniV3AddressPath.length)")
485            assert(uniV3AddressPath[0].equals(yieldTokenEVMAddress),
486                message: "UniswapV3 swap path does not match - expected path[0] to be \(yieldTokenEVMAddress.toString()) but found \(uniV3AddressPath[0].toString())") 
487            let collateralUniV3FeePathConfig = collateralConfig["yieldToCollateralUniV3FeePaths"] as? {Type: [UInt32]}
488                ?? panic("Could not find UniswapV3 fee paths config when creating Strategy \(type.identifier) with collateral \(collateralType.identifier)")
489            let uniV3FeePath = collateralUniV3FeePathConfig[collateralType]
490                ?? panic("Could not find UniswapV3 fee path for collateral type \(collateralType.identifier)")
491            assert(uniV3FeePath.length > 0, message: "Invalid Uniswap V3 fee path length of \(uniV3FeePath.length)")
492            // initialize the swapper used for recollateralization of the lending position as YIELD increases in value
493            let yieldToFlowSwapper = UniswapV3SwapConnectors.Swapper(
494                    factoryAddress: univ3FactoryEVMAddress,
495                    routerAddress: univ3RouterEVMAddress,
496                    quoterAddress: univ3QuoterEVMAddress,
497                    tokenPath: uniV3AddressPath,
498                    feePath: uniV3FeePath,
499                    inVault: yieldTokenType,
500                    outVault: collateralType,
501                    coaCapability: FlowVaultsStrategies._getCOACapability(),
502                    uniqueID: uniqueID
503                )
504            // allows for YIELD to be deposited to the Position as the collateral basis
505            let positionSwapSink = SwapConnectors.SwapSink(swapper: yieldToFlowSwapper, sink: positionSink, uniqueID: uniqueID)
506
507            // set the AutoBalancer's rebalance Sink which it will use to deposit overflown value, recollateralizing
508            // the position
509            autoBalancer.setSink(positionSwapSink, updateSinkID: true)
510
511            return <-create mUSDCStrategy(
512                id: DeFiActions.createUniqueIdentifier(),
513                collateralType: collateralType,
514                position: position
515            )
516        }
517    }
518
519    access(all) entitlement Configure
520
521    /// This resource enables the issuance of StrategyComposers, thus safeguarding the issuance of Strategies which
522    /// may utilize resource consumption (i.e. account storage). Since TracerStrategy creation consumes account storage
523    /// via configured AutoBalancers
524    access(all) resource StrategyComposerIssuer : FlowVaults.StrategyComposerIssuer {
525        /// { StrategyComposer Type: { Strategy Type: { Collateral Type: { String: AnyStruct } } } }
526        access(all) let configs: {Type: {Type: {Type: {String: AnyStruct}}}}
527
528        init(configs: {Type: {Type: {Type: {String: AnyStruct}}}}) {
529            self.configs = configs
530        }
531
532        access(all) view fun getSupportedComposers(): {Type: Bool} {
533            return { 
534                Type<@mUSDCStrategyComposer>(): true,
535                Type<@TracerStrategyComposer>(): true
536            }
537        }
538        access(all) fun issueComposer(_ type: Type): @{FlowVaults.StrategyComposer} {
539            pre {
540                self.getSupportedComposers()[type] == true:
541                "Unsupported StrategyComposer \(type.identifier) requested"
542                (&self.configs[type] as &{Type: {Type: {String: AnyStruct}}}?) != nil:
543                "Could not find config for StrategyComposer \(type.identifier)"
544            }
545            switch type {
546            case Type<@mUSDCStrategyComposer>():
547                return <- create mUSDCStrategyComposer(self.configs[type]!)
548            case Type<@TracerStrategyComposer>():
549                return <- create TracerStrategyComposer()
550            default:
551                panic("Unsupported StrategyComposer \(type.identifier) requested")
552            }
553        }
554        access(Configure) fun upsertConfigFor(composer: Type, config: {Type: {Type: {String: AnyStruct}}}) {
555            pre {
556                self.getSupportedComposers()[composer] == true:
557                "Unsupported StrategyComposer Type \(composer.identifier)"
558            }
559            for stratType in config.keys {
560                assert(stratType.isSubtype(of: Type<@{FlowVaults.Strategy}>()),
561                    message: "Invalid config key \(stratType.identifier) - not a FlowVaults.Strategy Type")
562                for collateralType in config[stratType]!.keys {
563                    assert(collateralType.isSubtype(of: Type<@{FungibleToken.Vault}>()),
564                        message: "Invalid config key at config[\(stratType.identifier)] - \(collateralType.identifier) is not a FungibleToken.Vault")
565                }
566            }
567            self.configs[composer] = config
568        }
569    }
570
571    /// Returns the COA capability for this account
572    /// TODO: this is temporary until we have a better way to pass user's COAs to inner connectors
573    access(self)
574    fun _getCOACapability(): Capability<auth(EVM.Call, EVM.Bridge, EVM.Owner) &EVM.CadenceOwnedAccount> {
575        let coaCap = self.account.capabilities.storage.issue<auth(EVM.Call, EVM.Bridge, EVM.Owner) &EVM.CadenceOwnedAccount>(/storage/evm)
576        assert(coaCap.check(), message: "Could not issue COA capability")
577        return coaCap
578    }
579
580    /// Returns a FungibleTokenConnectors.VaultSinkAndSource used to subsidize cross VM token movement in contract-
581    /// defined strategies.
582    access(self)
583    fun _createFeeSource(withID: DeFiActions.UniqueIdentifier?): {DeFiActions.Sink, DeFiActions.Source} {
584        let capPath = /storage/strategiesFeeSource
585        if self.account.storage.type(at: capPath) == nil {
586            let cap = self.account.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(/storage/flowTokenVault)
587            self.account.storage.save(cap, to: capPath)
588        }
589        let vaultCap = self.account.storage.copy<Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>>(from: capPath)
590            ?? panic("Could not find fee source Capability at \(capPath)")
591        return FungibleTokenConnectors.VaultSinkAndSource(
592            min: nil,
593            max: nil,
594            vault: vaultCap,
595            uniqueID: withID
596        )
597    }
598
599    init(
600        univ3FactoryEVMAddress: String,
601        univ3RouterEVMAddress: String,
602        univ3QuoterEVMAddress: String,
603        yieldTokenEVMAddress: String,
604        recollateralizationUniV3AddressPath: [String],
605        recollateralizationUniV3FeePath: [UInt32],
606    ) {
607        self.univ3FactoryEVMAddress = EVM.addressFromString(univ3FactoryEVMAddress)
608        self.univ3RouterEVMAddress = EVM.addressFromString(univ3RouterEVMAddress)
609        self.univ3QuoterEVMAddress = EVM.addressFromString(univ3QuoterEVMAddress)
610        self.yieldTokenEVMAddress = EVM.addressFromString(yieldTokenEVMAddress)
611        self.IssuerStoragePath = StoragePath(identifier: "FlowVaultsStrategyComposerIssuer_\(self.account.address)")!
612
613        let initialCollateralType = Type<@FlowToken.Vault>()
614        let moetType = Type<@MOET.Vault>()
615        let moetEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: Type<@MOET.Vault>())
616            ?? panic("Could not find EVM address for \(moetType.identifier) - ensure the asset is onboarded to the VM Bridge")
617        let yieldTokenEVMAddress = EVM.addressFromString(yieldTokenEVMAddress)
618
619        let swapAddressPath: [EVM.EVMAddress] = []
620        for hex in recollateralizationUniV3AddressPath {
621            swapAddressPath.append(EVM.addressFromString(hex))
622        }
623
624        let configs: {Type: {Type: {Type: {String: AnyStruct}}}} = {
625                Type<@mUSDCStrategyComposer>(): {
626                    Type<@mUSDCStrategy>(): {
627                        initialCollateralType: {
628                            "univ3FactoryEVMAddress": self.univ3FactoryEVMAddress,
629                            "univ3RouterEVMAddress": self.univ3RouterEVMAddress,
630                            "univ3QuoterEVMAddress": self.univ3QuoterEVMAddress,
631                            "yieldTokenEVMAddress": self.yieldTokenEVMAddress,
632                            "yieldToCollateralUniV3AddressPaths": {
633                                initialCollateralType: swapAddressPath
634                            },
635                            "yieldToCollateralUniV3FeePaths": {
636                                initialCollateralType: recollateralizationUniV3FeePath
637                            }
638                        }
639                    }
640                },
641                Type<@TracerStrategyComposer>(): {
642                    Type<@TracerStrategy>(): {}
643                }
644            }
645        self.account.storage.save(<-create StrategyComposerIssuer(configs: configs), to: self.IssuerStoragePath)
646
647        // TODO: this is temporary until we have a better way to pass user's COAs to inner connectors
648        // create a COA in this account
649        if self.account.storage.type(at: /storage/evm) == nil {
650            self.account.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm)
651            let cap = self.account.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(/storage/evm)
652            self.account.capabilities.publish(cap, at: /public/evm)
653        }
654    }
655}
656