DeploySEALED

◆░╳^█$○▫○□╱╱&^○▫░#@*█▓○▓╳╳▫░▒?%~╱~□!╳╲!╲◇░~$~^░▫◇@◇▪!^▒*?&**▫$▓█

Transaction ID

Timestamp

Feb 24, 2026, 01:06:28 PM UTC
4d ago

Block Height

143,267,112

Computation

0

Execution Fee

0.01094 FLOW

Transaction Summary

Deploy

Contract deployment

Contract deployment

Script Arguments

0nameString
FlowYieldVaultsStrategiesV2
1codeString
// standards import FungibleToken from 0xf233dcee88fe0abe import EVM from 0xe467b9dd11fa00df // DeFiActions import DeFiActionsUtils from 0x6d888f175c158410 import DeFiActions from 0x6d888f175c158410 import SwapConnectors from 0xe1a479f0cb911df9 import FungibleTokenConnectors from 0x0c237e1265caa7a3 // amm integration import UniswapV3SwapConnectors from 0xa7825d405ac89518 import ERC4626SwapConnectors from 0x04f5ae6bef48c1fc import MorphoERC4626SwapConnectors from 0x251032a66e9700ef import ERC4626Utils from 0x04f5ae6bef48c1fc // Lending protocol import FlowALPv0 from 0x6b00ff876c299c61 // FlowYieldVaults platform import FlowYieldVaults from 0xb1d63873c3cc9f79 import FlowYieldVaultsAutoBalancers from 0xb1d63873c3cc9f79 // scheduler import FlowTransactionScheduler from 0xe467b9dd11fa00df // tokens import MOET from 0x6b00ff876c299c61 // vm bridge import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141 // live oracles import ERC4626PriceOracles from 0x04f5ae6bef48c1fc /// FlowYieldVaultsStrategiesV2 /// /// This contract defines Strategies used in the FlowYieldVaults platform. /// /// A Strategy instance can be thought of as objects wrapping a stack of DeFiActions connectors wired together to /// (optimally) generate some yield on initial deposits. Strategies can be simple such as swapping into a yield-bearing /// asset (such as stFLOW) or more complex DeFiActions stacks. /// /// A StrategyComposer is tasked with the creation of a supported Strategy. It's within the stacking of DeFiActions /// connectors that the true power of the components lies. /// access(all) contract FlowYieldVaultsStrategiesV2 { access(all) let univ3FactoryEVMAddress: EVM.EVMAddress access(all) let univ3RouterEVMAddress: EVM.EVMAddress access(all) let univ3QuoterEVMAddress: EVM.EVMAddress access(all) let config: {String: AnyStruct} /// Canonical StoragePath where the StrategyComposerIssuer should be stored access(all) let IssuerStoragePath: StoragePath access(all) struct CollateralConfig { access(all) let yieldTokenEVMAddress: EVM.EVMAddress access(all) let yieldToCollateralUniV3AddressPath: [EVM.EVMAddress] access(all) let yieldToCollateralUniV3FeePath: [UInt32] init( yieldTokenEVMAddress: EVM.EVMAddress, yieldToCollateralUniV3AddressPath: [EVM.EVMAddress], yieldToCollateralUniV3FeePath: [UInt32] ) { pre { yieldToCollateralUniV3AddressPath.length > 1: "Invalid UniV3 path length" yieldToCollateralUniV3FeePath.length == yieldToCollateralUniV3AddressPath.length - 1: "Invalid UniV3 fee path length" yieldToCollateralUniV3AddressPath[0].equals(yieldTokenEVMAddress): "UniV3 path must start with yield token" } self.yieldTokenEVMAddress = yieldTokenEVMAddress self.yieldToCollateralUniV3AddressPath = yieldToCollateralUniV3AddressPath self.yieldToCollateralUniV3FeePath = yieldToCollateralUniV3FeePath } } /// This strategy uses FUSDEV vault access(all) resource FUSDEVStrategy : FlowYieldVaults.Strategy, DeFiActions.IdentifiableResource { /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol- /// specific Identifier to associated connectors on construction access(contract) var uniqueID: DeFiActions.UniqueIdentifier? access(self) let position: @FlowALPv0.Position access(self) var sink: {DeFiActions.Sink} access(self) var source: {DeFiActions.Source} init(id: DeFiActions.UniqueIdentifier, collateralType: Type, position: @FlowALPv0.Position) { self.uniqueID = id self.sink = position.createSink(type: collateralType) self.source = position.createSourceWithOptions(type: collateralType, pullFromTopUpSource: true) self.position <-position } // Inherited from FlowYieldVaults.Strategy default implementation // access(all) view fun isSupportedCollateralType(_ type: Type): Bool access(all) view fun getSupportedCollateralTypes(): {Type: Bool} { return { self.sink.getSinkType(): true } } /// Returns the amount available for withdrawal via the inner Source access(all) fun availableBalance(ofToken: Type): UFix64 { return ofToken == self.source.getSourceType() ? self.source.minimumAvailable() : 0.0 } /// Deposits up to the inner Sink's capacity from the provided authorized Vault reference access(all) fun deposit(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) { self.sink.depositCapacity(from: from) } /// Withdraws up to the max amount, returning the withdrawn Vault. If the requested token type is unsupported, /// an empty Vault is returned. access(FungibleToken.Withdraw) fun withdraw(maxAmount: UFix64, ofToken: Type): @{FungibleToken.Vault} { if ofToken != self.source.getSourceType() { return <- DeFiActionsUtils.getEmptyVault(ofToken) } return <- self.source.withdrawAvailable(maxAmount: maxAmount) } /// Executed when a Strategy is burned, cleaning up the Strategy's stored AutoBalancer access(contract) fun burnCallback() { FlowYieldVaultsAutoBalancers._cleanupAutoBalancer(id: self.id()!) } access(all) fun getComponentInfo(): DeFiActions.ComponentInfo { return DeFiActions.ComponentInfo( type: self.getType(), id: self.id(), innerComponents: [ self.sink.getComponentInfo(), self.source.getComponentInfo() ] ) } access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? { return self.uniqueID } access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) { self.uniqueID = id } } access(all) struct TokenBundle { access(all) let moetTokenType: Type access(all) let moetTokenEVMAddress: EVM.EVMAddress access(all) let yieldTokenType: Type access(all) let yieldTokenEVMAddress: EVM.EVMAddress access(all) let underlying4626AssetType: Type access(all) let underlying4626AssetEVMAddress: EVM.EVMAddress init( moetTokenType: Type, moetTokenEVMAddress: EVM.EVMAddress, yieldTokenType: Type, yieldTokenEVMAddress: EVM.EVMAddress, underlying4626AssetType: Type, underlying4626AssetEVMAddress: EVM.EVMAddress ) { self.moetTokenType = moetTokenType self.moetTokenEVMAddress = moetTokenEVMAddress self.yieldTokenType = yieldTokenType self.yieldTokenEVMAddress = yieldTokenEVMAddress self.underlying4626AssetType = underlying4626AssetType self.underlying4626AssetEVMAddress = underlying4626AssetEVMAddress } } /// Returned bundle for stored AutoBalancer interactions (reference + caps) access(all) struct AutoBalancerIO { access(all) let autoBalancer: auth(DeFiActions.Auto, DeFiActions.Set, DeFiActions.Get, DeFiActions.Schedule, FungibleToken.Withdraw) &DeFiActions.AutoBalancer access(all) let sink: {DeFiActions.Sink} access(all) let source: {DeFiActions.Source} init( autoBalancer: auth(DeFiActions.Auto, DeFiActions.Set, DeFiActions.Get, DeFiActions.Schedule, FungibleToken.Withdraw) &DeFiActions.AutoBalancer, sink: {DeFiActions.Sink}, source: {DeFiActions.Source} ) { self.sink = sink self.source = source self.autoBalancer = autoBalancer } } /// This StrategyComposer builds a Strategy that uses MorphoERC4626 vault access(all) resource MorphoERC4626StrategyComposer : FlowYieldVaults.StrategyComposer { /// { Strategy Type: { Collateral Type: FlowYieldVaultsStrategiesV2.CollateralConfig } } access(self) let config: {Type: {Type: FlowYieldVaultsStrategiesV2.CollateralConfig}} init(_ config: {Type: {Type: FlowYieldVaultsStrategiesV2.CollateralConfig}}) { self.config = config } /// Returns the Types of Strategies composed by this StrategyComposer access(all) view fun getComposedStrategyTypes(): {Type: Bool} { let composed: {Type: Bool} = {} for t in self.config.keys { composed[t] = true } return composed } /// Returns the Vault types which can be used to initialize a given Strategy access(all) view fun getSupportedInitializationVaults(forStrategy: Type): {Type: Bool} { let supported: {Type: Bool} = {} if let strategyConfig = &self.config[forStrategy] as &{Type: FlowYieldVaultsStrategiesV2.CollateralConfig}? { for collateralType in strategyConfig.keys { supported[collateralType] = true } } return supported } access(self) view fun _supportsCollateral(forStrategy: Type, collateral: Type): Bool { if let strategyConfig = self.config[forStrategy] { return strategyConfig[collateral] != nil } return false } /// Returns the Vault types which can be deposited to a given Strategy instance if it was initialized with the /// provided Vault type access(all) view fun getSupportedInstanceVaults(forStrategy: Type, initializedWith: Type): {Type: Bool} { return self._supportsCollateral(forStrategy: forStrategy, collateral: initializedWith) ? { initializedWith: true } : {} } /// Composes a Strategy of the given type with the provided funds access(all) fun createStrategy( _ type: Type, uniqueID: DeFiActions.UniqueIdentifier, withFunds: @{FungibleToken.Vault} ): @{FlowYieldVaults.Strategy} { pre { self.config[type] != nil: "Unsupported strategy type \(type.identifier)" } let collateralType = withFunds.getType() let collateralConfig = self._getCollateralConfig( strategyType: type, collateralType: collateralType ) let tokens = self._resolveTokenBundle(collateralConfig: collateralConfig) // Oracle used by AutoBalancer (tracks NAV of ERC4626 vault) let yieldTokenOracle = self._createYieldTokenOracle( yieldTokenEVMAddress: tokens.yieldTokenEVMAddress, underlyingAssetType: tokens.underlying4626AssetType, uniqueID: uniqueID ) // Create recurring config for automatic rebalancing let recurringConfig = FlowYieldVaultsStrategiesV2._createRecurringConfig(withID: uniqueID) // Create/store/publish/register AutoBalancer (returns authorized ref) let balancerIO = self._initAutoBalancerAndIO( oracle: yieldTokenOracle, yieldTokenType: tokens.yieldTokenType, recurringConfig: recurringConfig, uniqueID: uniqueID ) // Swappers: MOET <-> YIELD (YIELD is ERC4626 vault token) let moetToYieldSwapper = self._createMoetToYieldSwapper(strategyType: type, tokens: tokens, uniqueID: uniqueID) let yieldToMoetSwapper = self._createYieldToMoetSwapper(strategyType: type, tokens: tokens, uniqueID: uniqueID) // AutoBalancer-directed swap IO let abaSwapSink = SwapConnectors.SwapSink( swapper: moetToYieldSwapper, sink: balancerIO.sink, uniqueID: uniqueID ) let abaSwapSource = SwapConnectors.SwapSource( swapper: yieldToMoetSwapper, source: balancerIO.source, uniqueID: uniqueID ) // Open FlowALPv0 position let position <- self._openCreditPosition( funds: <-withFunds, issuanceSink: abaSwapSink, repaymentSource: abaSwapSource ) // Position Sink/Source (only Sink needed here, Source stays inside Strategy impl) let positionSink = position.createSinkWithOptions(type: collateralType, pushToDrawDownSink: true) // Yield -> Collateral swapper for recollateralization let yieldToCollateralSwapper = self._createYieldToCollateralSwapper( collateralConfig: collateralConfig, yieldTokenEVMAddress: tokens.yieldTokenEVMAddress, yieldTokenType: tokens.yieldTokenType, collateralType: collateralType, uniqueID: uniqueID ) let positionSwapSink = SwapConnectors.SwapSink( swapper: yieldToCollateralSwapper, sink: positionSink, uniqueID: uniqueID ) // Set AutoBalancer sink for overflow -> recollateralize balancerIO.autoBalancer.setSink(positionSwapSink, updateSinkID: true) switch type { case Type<@FUSDEVStrategy>(): return <-create FUSDEVStrategy( id: uniqueID, collateralType: collateralType, position: <-position ) default: panic("Unsupported strategy type \(type.identifier)") } } /* =========================== Helpers =========================== */ access(self) fun _getCollateralConfig( strategyType: Type, collateralType: Type ): FlowYieldVaultsStrategiesV2.CollateralConfig { let strategyConfig = self.config[strategyType] ?? panic( "Could not find a config for Strategy \(strategyType.identifier) initialized with \(collateralType.identifier)" ) return strategyConfig[collateralType] ?? panic( "Could not find config for collateral \(collateralType.identifier) when creating Strategy \(strategyType.identifier)" ) } access(self) fun _resolveTokenBundle( collateralConfig: FlowYieldVaultsStrategiesV2.CollateralConfig ): FlowYieldVaultsStrategiesV2.TokenBundle { // MOET let moetTokenType = Type<@MOET.Vault>() let moetTokenEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: moetTokenType) ?? panic("Token Vault type \(moetTokenType.identifier) has not yet been registered with the VMbridge") // YIELD (ERC4626 vault token) let yieldTokenEVMAddress = collateralConfig.yieldTokenEVMAddress let yieldTokenType = FlowEVMBridgeConfig.getTypeAssociated(with: yieldTokenEVMAddress) ?? panic( "Could not retrieve the VM Bridge associated Type for the yield token address \(yieldTokenEVMAddress.toString())" ) // UNDERLYING asset of the ERC4626 vault let underlying4626AssetEVMAddress = ERC4626Utils.underlyingAssetEVMAddress(vault: yieldTokenEVMAddress) ?? panic( "Could not get the underlying asset's EVM address for ERC4626Vault \(yieldTokenEVMAddress.toString())" ) let underlying4626AssetType = FlowEVMBridgeConfig.getTypeAssociated(with: underlying4626AssetEVMAddress) ?? panic( "Could not retrieve the VM Bridge associated Type for the ERC4626 underlying asset \(underlying4626AssetEVMAddress.toString())" ) return FlowYieldVaultsStrategiesV2.TokenBundle( moetTokenType: moetTokenType, moetTokenEVMAddress: moetTokenEVMAddress, yieldTokenType: yieldTokenType, yieldTokenEVMAddress: yieldTokenEVMAddress, underlying4626AssetType: underlying4626AssetType, underlying4626AssetEVMAddress: underlying4626AssetEVMAddress ) } access(self) fun _createYieldTokenOracle( yieldTokenEVMAddress: EVM.EVMAddress, underlyingAssetType: Type, uniqueID: DeFiActions.UniqueIdentifier ): ERC4626PriceOracles.PriceOracle { return ERC4626PriceOracles.PriceOracle( vault: yieldTokenEVMAddress, asset: underlyingAssetType, uniqueID: uniqueID ) } access(self) fun _createUniV3Swapper( tokenPath: [EVM.EVMAddress], feePath: [UInt32], inVault: Type, outVault: Type, uniqueID: DeFiActions.UniqueIdentifier ): UniswapV3SwapConnectors.Swapper { return UniswapV3SwapConnectors.Swapper( factoryAddress: FlowYieldVaultsStrategiesV2.univ3FactoryEVMAddress, routerAddress: FlowYieldVaultsStrategiesV2.univ3RouterEVMAddress, quoterAddress: FlowYieldVaultsStrategiesV2.univ3QuoterEVMAddress, tokenPath: tokenPath, feePath: feePath, inVault: inVault, outVault: outVault, coaCapability: FlowYieldVaultsStrategiesV2._getCOACapability(), uniqueID: uniqueID ) } access(self) fun _createMoetToYieldSwapper( strategyType: Type, tokens: FlowYieldVaultsStrategiesV2.TokenBundle, uniqueID: DeFiActions.UniqueIdentifier ): SwapConnectors.MultiSwapper { // Direct MOET -> YIELD via AMM let moetToYieldAMM = self._createUniV3Swapper( tokenPath: [tokens.moetTokenEVMAddress, tokens.yieldTokenEVMAddress], feePath: [100], inVault: tokens.moetTokenType, outVault: tokens.yieldTokenType, uniqueID: uniqueID ) // MOET -> UNDERLYING via AMM let moetToUnderlying = self._createUniV3Swapper( tokenPath: [tokens.moetTokenEVMAddress, tokens.underlying4626AssetEVMAddress], feePath: [100], inVault: tokens.moetTokenType, outVault: tokens.underlying4626AssetType, uniqueID: uniqueID ) // UNDERLYING -> YIELD via ERC4626 vault // Morpho vaults use MorphoERC4626SwapConnectors; standard ERC4626 vaults use ERC4626SwapConnectors var underlyingTo4626: {DeFiActions.Swapper}? = nil if strategyType == Type<@FUSDEVStrategy>() { underlyingTo4626 = MorphoERC4626SwapConnectors.Swapper( vaultEVMAddress: tokens.yieldTokenEVMAddress, coa: FlowYieldVaultsStrategiesV2._getCOACapability(), feeSource: FlowYieldVaultsStrategiesV2._createFeeSource(withID: uniqueID), uniqueID: uniqueID, isReversed: false ) } else { underlyingTo4626 = ERC4626SwapConnectors.Swapper( asset: tokens.underlying4626AssetType, vault: tokens.yieldTokenEVMAddress, coa: FlowYieldVaultsStrategiesV2._getCOACapability(), feeSource: FlowYieldVaultsStrategiesV2._createFeeSource(withID: uniqueID), uniqueID: uniqueID ) } let seq = SwapConnectors.SequentialSwapper( swappers: [moetToUnderlying, underlyingTo4626!], uniqueID: uniqueID ) return SwapConnectors.MultiSwapper( inVault: tokens.moetTokenType, outVault: tokens.yieldTokenType, swappers: [moetToYieldAMM, seq], uniqueID: uniqueID ) } access(self) fun _createYieldToMoetSwapper( strategyType: Type, tokens: FlowYieldVaultsStrategiesV2.TokenBundle, uniqueID: DeFiActions.UniqueIdentifier ): SwapConnectors.MultiSwapper { // Direct YIELD -> MOET via AMM let yieldToMoetAMM = self._createUniV3Swapper( tokenPath: [tokens.yieldTokenEVMAddress, tokens.moetTokenEVMAddress], feePath: [100], inVault: tokens.yieldTokenType, outVault: tokens.moetTokenType, uniqueID: uniqueID ) // Reverse path: Morpho vaults support direct redeem; standard ERC4626 vaults use AMM-only path if strategyType == Type<@FUSDEVStrategy>() { // YIELD -> UNDERLYING redeem via MorphoERC4626 vault let yieldToUnderlying = MorphoERC4626SwapConnectors.Swapper( vaultEVMAddress: tokens.yieldTokenEVMAddress, coa: FlowYieldVaultsStrategiesV2._getCOACapability(), feeSource: FlowYieldVaultsStrategiesV2._createFeeSource(withID: uniqueID), uniqueID: uniqueID, isReversed: true ) // UNDERLYING -> MOET via AMM let underlyingToMoet = self._createUniV3Swapper( tokenPath: [tokens.underlying4626AssetEVMAddress, tokens.moetTokenEVMAddress], feePath: [100], inVault: tokens.underlying4626AssetType, outVault: tokens.moetTokenType, uniqueID: uniqueID ) let seq = SwapConnectors.SequentialSwapper( swappers: [yieldToUnderlying, underlyingToMoet], uniqueID: uniqueID ) return SwapConnectors.MultiSwapper( inVault: tokens.yieldTokenType, outVault: tokens.moetTokenType, swappers: [yieldToMoetAMM, seq], uniqueID: uniqueID ) } else { // Standard ERC4626: AMM-only reverse (no synchronous redeem support) return SwapConnectors.MultiSwapper( inVault: tokens.yieldTokenType, outVault: tokens.moetTokenType, swappers: [yieldToMoetAMM], uniqueID: uniqueID ) } } access(self) fun _initAutoBalancerAndIO( oracle: {DeFiActions.PriceOracle}, yieldTokenType: Type, recurringConfig: DeFiActions.AutoBalancerRecurringConfig?, uniqueID: DeFiActions.UniqueIdentifier ): FlowYieldVaultsStrategiesV2.AutoBalancerIO { // NOTE: This stores the AutoBalancer in FlowYieldVaultsAutoBalancers storage and returns an authorized ref. let autoBalancerRef = FlowYieldVaultsAutoBalancers._initNewAutoBalancer( oracle: oracle, vaultType: yieldTokenType, lowerThreshold: 0.95, upperThreshold: 1.05, rebalanceSink: nil, rebalanceSource: nil, recurringConfig: recurringConfig, uniqueID: uniqueID ) let sink = autoBalancerRef.createBalancerSink() ?? panic("Could not retrieve Sink from AutoBalancer with id \(uniqueID.id)") let source = autoBalancerRef.createBalancerSource() ?? panic("Could not retrieve Source from AutoBalancer with id \(uniqueID.id)") return FlowYieldVaultsStrategiesV2.AutoBalancerIO( autoBalancer: autoBalancerRef, sink: sink, source: source ) } access(self) fun _openCreditPosition( funds: @{FungibleToken.Vault}, issuanceSink: {DeFiActions.Sink}, repaymentSource: {DeFiActions.Source} ): @FlowALPv0.Position { let poolCap = FlowYieldVaultsStrategiesV2.account.storage.copy< Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool> >(from: FlowALPv0.PoolCapStoragePath) ?? panic("Missing or invalid pool capability") let poolRef = poolCap.borrow() ?? panic("Invalid Pool Cap") let position <- poolRef.createPosition( funds: <-funds, issuanceSink: issuanceSink, repaymentSource: repaymentSource, pushToDrawDownSink: true ) return <-position } access(self) fun _createYieldToCollateralSwapper( collateralConfig: FlowYieldVaultsStrategiesV2.CollateralConfig, yieldTokenEVMAddress: EVM.EVMAddress, yieldTokenType: Type, collateralType: Type, uniqueID: DeFiActions.UniqueIdentifier ): UniswapV3SwapConnectors.Swapper { // CollateralConfig.init already validates: // - path length > 1 // - fee length == path length - 1 // - path[0] == yield token // // Keep a defensive check in case configs were migrated / constructed elsewhere. let tokenPath = collateralConfig.yieldToCollateralUniV3AddressPath assert( tokenPath[0].equals(yieldTokenEVMAddress), message: "Config mismatch: expected yield token \(yieldTokenEVMAddress.toString()) but got \(tokenPath[0].toString())" ) return self._createUniV3Swapper( tokenPath: tokenPath, feePath: collateralConfig.yieldToCollateralUniV3FeePath, inVault: yieldTokenType, outVault: collateralType, uniqueID: uniqueID ) } } access(all) entitlement Configure access(self) fun makeCollateralConfig( yieldTokenEVMAddress: EVM.EVMAddress, yieldToCollateralAddressPath: [EVM.EVMAddress], yieldToCollateralFeePath: [UInt32] ): CollateralConfig { pre { yieldToCollateralAddressPath.length > 1: "Invalid Uniswap V3 swap path length" yieldToCollateralFeePath.length == yieldToCollateralAddressPath.length - 1: "Uniswap V3 fee path length must be path length - 1" yieldToCollateralAddressPath[0].equals(yieldTokenEVMAddress): "UniswapV3 swap path must start with yield token" } return CollateralConfig( yieldTokenEVMAddress: yieldTokenEVMAddress, yieldToCollateralUniV3AddressPath: yieldToCollateralAddressPath, yieldToCollateralUniV3FeePath: yieldToCollateralFeePath ) } /// This resource enables the issuance of StrategyComposers, thus safeguarding the issuance of Strategies which /// may utilize resource consumption (i.e. account storage). Since Strategy creation consumes account storage /// via configured AutoBalancers access(all) resource StrategyComposerIssuer : FlowYieldVaults.StrategyComposerIssuer { /// { StrategyComposer Type: { Strategy Type: { Collateral Type: FlowYieldVaultsStrategiesV2.CollateralConfig } } } access(all) var configs: {Type: {Type: {Type: FlowYieldVaultsStrategiesV2.CollateralConfig}}} init(configs: {Type: {Type: {Type: FlowYieldVaultsStrategiesV2.CollateralConfig}}}) { self.configs = configs } access(all) view fun hasConfig( composer: Type, strategy: Type, collateral: Type ): Bool { if let composerConfig = self.configs[composer] { if let strategyConfig = composerConfig[strategy] { return strategyConfig[collateral] != nil } } return false } access(all) view fun getSupportedComposers(): {Type: Bool} { return { Type<@MorphoERC4626StrategyComposer>(): true } } access(self) view fun isSupportedComposer(_ type: Type): Bool { return type == Type<@MorphoERC4626StrategyComposer>() } access(all) fun issueComposer(_ type: Type): @{FlowYieldVaults.StrategyComposer} { pre { self.isSupportedComposer(type) == true: "Unsupported StrategyComposer \(type.identifier) requested" self.configs[type] != nil: "Could not find config for StrategyComposer \(type.identifier)" } switch type { case Type<@MorphoERC4626StrategyComposer>(): return <- create MorphoERC4626StrategyComposer(self.configs[type]!) default: panic("Unsupported StrategyComposer \(type.identifier) requested") } } access(Configure) fun upsertConfigFor( composer: Type, config: {Type: {Type: FlowYieldVaultsStrategiesV2.CollateralConfig}} ) { pre { self.isSupportedComposer(composer) == true: "Unsupported StrategyComposer Type \(composer.identifier)" } // Validate keys for stratType in config.keys { assert(stratType.isSubtype(of: Type<@{FlowYieldVaults.Strategy}>()), message: "Invalid config key \(stratType.identifier) - not a FlowYieldVaults.Strategy Type") for collateralType in config[stratType]!.keys { assert(collateralType.isSubtype(of: Type<@{FungibleToken.Vault}>()), message: "Invalid config key at config[\(stratType.identifier)] - \(collateralType.identifier) is not a FungibleToken.Vault") } } // Merge instead of overwrite let existingComposerConfig = self.configs[composer] ?? {} var mergedComposerConfig = existingComposerConfig for stratType in config.keys { let newPerCollateral = config[stratType]! let existingPerCollateral = mergedComposerConfig[stratType] ?? {} var mergedPerCollateral: {Type: FlowYieldVaultsStrategiesV2.CollateralConfig} = existingPerCollateral for collateralType in newPerCollateral.keys { mergedPerCollateral[collateralType] = newPerCollateral[collateralType]! } mergedComposerConfig[stratType] = mergedPerCollateral } self.configs[composer] = mergedComposerConfig } access(Configure) fun addOrUpdateCollateralConfig( composer: Type, strategyType: Type, collateralVaultType: Type, yieldTokenEVMAddress: EVM.EVMAddress, yieldToCollateralAddressPath: [EVM.EVMAddress], yieldToCollateralFeePath: [UInt32] ) { pre { self.isSupportedComposer(composer) == true: "Unsupported StrategyComposer Type \(composer.identifier)" strategyType.isSubtype(of: Type<@{FlowYieldVaults.Strategy}>()): "Strategy type \(strategyType.identifier) is not a FlowYieldVaults.Strategy" collateralVaultType.isSubtype(of: Type<@{FungibleToken.Vault}>()): "Collateral type \(collateralVaultType.identifier) is not a FungibleToken.Vault" } // Base struct with shared addresses var base = FlowYieldVaultsStrategiesV2.makeCollateralConfig( yieldTokenEVMAddress: yieldTokenEVMAddress, yieldToCollateralAddressPath: yieldToCollateralAddressPath, yieldToCollateralFeePath: yieldToCollateralFeePath ) // Wrap into the nested config expected by upsertConfigFor let singleCollateralConfig = { strategyType: { collateralVaultType: base } } self.upsertConfigFor(composer: composer, config: singleCollateralConfig) } access(Configure) fun purgeConfig() { self.configs = { Type<@MorphoERC4626StrategyComposer>(): { Type<@FUSDEVStrategy>(): {} as {Type: FlowYieldVaultsStrategiesV2.CollateralConfig} } } } } /// Returns the COA capability for this account /// TODO: this is temporary until we have a better way to pass user's COAs to inner connectors access(self) fun _getCOACapability(): Capability<auth(EVM.Call, EVM.Bridge, EVM.Owner) &EVM.CadenceOwnedAccount> { let coaCap = self.account.capabilities.storage.issue<auth(EVM.Call, EVM.Bridge, EVM.Owner) &EVM.CadenceOwnedAccount>(/storage/evm) assert(coaCap.check(), message: "Could not issue COA capability") return coaCap } /// Returns a FungibleTokenConnectors.VaultSinkAndSource used to subsidize cross VM token movement in contract- /// defined strategies. access(self) fun _createFeeSource(withID: DeFiActions.UniqueIdentifier?): {DeFiActions.Sink, DeFiActions.Source} { let capPath = /storage/strategiesFeeSource if self.account.storage.type(at: capPath) == nil { let cap = self.account.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(/storage/flowTokenVault) self.account.storage.save(cap, to: capPath) } let vaultCap = self.account.storage.copy<Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>>(from: capPath) ?? panic("Could not find fee source Capability at \(capPath)") return FungibleTokenConnectors.VaultSinkAndSource( min: nil, max: nil, vault: vaultCap, uniqueID: withID ) } /// Creates an AutoBalancerRecurringConfig for scheduled rebalancing. /// The txnFunder uses the contract's FlowToken vault to pay for scheduling fees. access(self) fun _createRecurringConfig(withID: DeFiActions.UniqueIdentifier?): DeFiActions.AutoBalancerRecurringConfig { // Create txnFunder that can provide/accept FLOW for scheduling fees let txnFunder = self._createTxnFunder(withID: withID) return DeFiActions.AutoBalancerRecurringConfig( interval: 60 * 10, // Rebalance every 10 minutes priority: FlowTransactionScheduler.Priority.Medium, executionEffort: 800, forceRebalance: false, txnFunder: txnFunder ) } /// Creates a Sink+Source for the AutoBalancer to use for scheduling fees access(self) fun _createTxnFunder(withID: DeFiActions.UniqueIdentifier?): {DeFiActions.Sink, DeFiActions.Source} { let capPath = /storage/autoBalancerTxnFunder if self.account.storage.type(at: capPath) == nil { let cap = self.account.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>(/storage/flowTokenVault) self.account.storage.save(cap, to: capPath) } let vaultCap = self.account.storage.copy<Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>>(from: capPath) ?? panic("Could not find txnFunder Capability at \(capPath)") return FungibleTokenConnectors.VaultSinkAndSource( min: nil, max: nil, vault: vaultCap, uniqueID: withID ) } init( univ3FactoryEVMAddress: String, univ3RouterEVMAddress: String, univ3QuoterEVMAddress: String, ) { self.univ3FactoryEVMAddress = EVM.addressFromString(univ3FactoryEVMAddress) self.univ3RouterEVMAddress = EVM.addressFromString(univ3RouterEVMAddress) self.univ3QuoterEVMAddress = EVM.addressFromString(univ3QuoterEVMAddress) self.IssuerStoragePath = StoragePath(identifier: "FlowYieldVaultsStrategyV2ComposerIssuer_\(self.account.address)")! self.config = {} let moetType = Type<@MOET.Vault>() if FlowEVMBridgeConfig.getEVMAddressAssociated(with: Type<@MOET.Vault>()) == nil { panic("Could not find EVM address for \(moetType.identifier) - ensure the asset is onboarded to the VM Bridge") } let configs = { Type<@MorphoERC4626StrategyComposer>(): { Type<@FUSDEVStrategy>(): {} as {Type: FlowYieldVaultsStrategiesV2.CollateralConfig} } } self.account.storage.save(<-create StrategyComposerIssuer(configs: configs), to: self.IssuerStoragePath) // TODO: this is temporary until we have a better way to pass user's COAs to inner connectors // create a COA in this account if self.account.storage.type(at: /storage/evm) == nil { self.account.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) let cap = self.account.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(/storage/evm) self.account.capabilities.publish(cap, at: /public/evm) } } }

Cadence Script

1transaction(name: String, code: String ,arg0:String,arg1:String,arg2:String) {
2		prepare(signer: auth(AddContract) &Account) {
3			signer.contracts.add(name: name, code: code.utf8 ,arg0,arg1,arg2)
4		}
5	}