DeploySEALED

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

Transaction ID

Timestamp

Nov 27, 2025, 08:41:54 PM UTC
3mo ago

Block Height

134,184,115

Computation

0

Execution Fee

0.00003523 FLOW

Transaction Summary

Deploy

Contract deployment

Contract deployment

Script Arguments

0nameString
DCATransactionHandlerV2
1codeString
import FlowTransactionScheduler from 0xe467b9dd11fa00df import FlowTransactionSchedulerUtils from 0xe467b9dd11fa00df import DCAVaultActions from 0x17ae3b1b0b0d50db import DeFiActions from 0x17ae3b1b0b0d50db import IncrementFiSwapperConnector from 0x17ae3b1b0b0d50db import UniswapV2SwapperConnectorV2 from 0x17ae3b1b0b0d50db import UniswapV3SwapperConnectorV2 from 0x17ae3b1b0b0d50db import EVMTokenRegistry from 0x17ae3b1b0b0d50db import SwapRouter from 0xa6850776a94e6551 import SwapFactory from 0xb063c16cac85dbd1 import FlowToken from 0x1654653399040a61 import FungibleToken from 0xf233dcee88fe0abe import PriceOracle from 0x17ae3b1b0b0d50db import SimplePriceOracle from 0x17ae3b1b0b0d50db import EVM from 0xe467b9dd11fa00df /// DCATransactionHandlerV2: Forte scheduled transaction handler for DCA purchases /// /// This V2 contract implements the FlowTransactionScheduler.TransactionHandler interface /// to enable automated, recurring DCA purchases executed by Forte's on-chain scheduler. /// /// V2 Features: /// - Full COA-based EVM swap execution /// - Support for UniswapV2 and UniswapV3 on Flow EVM /// - Token bridging between Cadence and EVM /// - Cron-style recurring purchase scheduling /// - FlowActions integration for DEX-agnostic swaps /// access(all) contract DCATransactionHandlerV2 { /// Events access(all) event DCATransactionExecuted(planId: UInt64, scheduledTxId: UInt64, amountSpent: UFix64, amountReceived: UFix64) access(all) event DCATransactionScheduled(planId: UInt64, executionTime: UFix64, priority: String) access(all) event DCASeriesCompleted(planId: UInt64, totalExecutions: UInt64) access(all) event DCATransactionFailed(planId: UInt64, scheduledTxId: UInt64, reason: String) access(all) event HandlerCreated(address: Address) /// EVM-specific events for monitoring access(all) event EVMSwapExecuted(planId: UInt64, dexType: String, amountIn: UFix64, amountOut: UFix64) access(all) event EVMSwapFailed(planId: UInt64, dexType: String, reason: String) /// Storage paths access(all) let HandlerStoragePath: StoragePath access(all) let HandlerPublicPath: PublicPath /// Execution data structure passed to scheduled transactions /// NOTE: Minimal design to stay under Forte's 1KB data size limit /// V2 includes COA capability for EVM swaps access(all) struct ExecutionData { /// DCA plan identifier access(all) let planId: UInt64 /// Address of the vault owner access(all) let vaultOwner: Address /// Execution priority access(all) let priority: FlowTransactionScheduler.Priority /// Execution effort (gas budget) access(all) let executionEffort: UInt64 /// For recurring DCA: interval between purchases access(all) let interval: UFix64? /// For recurring DCA: maximum executions (nil = unlimited) access(all) let maxExec: UInt64? /// Current execution count access(all) var count: UInt64 /// Base timestamp for calculating next execution access(all) let baseTime: UFix64 /// Required capabilities (minimal set) access(all) let feeCap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}> access(all) let mgrCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}> access(all) let handlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}> /// COA capability for EVM swaps (required for V2 EVM connectors) access(all) let coaCap: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>? init( planId: UInt64, vaultOwner: Address, priority: FlowTransactionScheduler.Priority, executionEffort: UInt64, interval: UFix64?, maxExec: UInt64?, count: UInt64, baseTime: UFix64, feeCap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>, mgrCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>, handlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>, coaCap: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>? ) { self.planId = planId self.vaultOwner = vaultOwner self.priority = priority self.executionEffort = executionEffort self.interval = interval self.maxExec = maxExec self.count = count self.baseTime = baseTime self.feeCap = feeCap self.mgrCap = mgrCap self.handlerCap = handlerCap self.coaCap = coaCap } /// Check if this DCA series should continue access(all) fun shouldContinue(): Bool { if let max = self.maxExec { return self.count < max } return true } /// Get next execution time (cron-style) access(all) fun getNextExecutionTime(): UFix64 { if let interval = self.interval { let elapsedTime = getCurrentBlock().timestamp - self.baseTime let elapsedIntervals = UInt64(elapsedTime / interval) return self.baseTime + (UFix64(elapsedIntervals + 1) * interval) } panic("No interval set for recurring DCA") } /// Create updated config for next execution access(all) fun withIncrementedCount(): ExecutionData { return ExecutionData( planId: self.planId, vaultOwner: self.vaultOwner, priority: self.priority, executionEffort: self.executionEffort, interval: self.interval, maxExec: self.maxExec, count: self.count + 1, baseTime: self.baseTime, feeCap: self.feeCap, mgrCap: self.mgrCap, handlerCap: self.handlerCap, coaCap: self.coaCap ) } } /// Transaction Handler resource access(all) resource Handler: FlowTransactionScheduler.TransactionHandler { /// Execute a scheduled DCA purchase access(FlowTransactionScheduler.Execute) fun executeTransaction( id: UInt64, data: AnyStruct? ) { pre { id > 0: "Transaction ID must be positive" data != nil: "Execution data cannot be nil" } // Parse execution data let executionData = data as! ExecutionData? ?? panic("Invalid execution data") // Get vault reference from owner's account let vaultCap = getAccount(executionData.vaultOwner) .capabilities.get<&DCAVaultActions.Vault>(DCAVaultActions.VaultPublicPath) let vault = vaultCap.borrow() ?? panic("Could not borrow vault reference") // Get the DCA plan let plan = vault.getPlan(planId: executionData.planId) ?? panic("Plan not found") // Verify plan is active assert(plan.status == DCAVaultActions.PlanStatus.active, message: "Plan is not active") assert(plan.isReadyForPurchase(), message: "Plan not ready for purchase") // Check trigger conditions if set var currentPrice: UFix64? = nil if plan.triggerType != DCAVaultActions.TriggerType.none { let targetSymbol = plan.tokenPair.targetToken // Fetch price with 5 minute staleness tolerance if let priceDataAny = SimplePriceOracle.getPriceWithMaxAge(symbol: targetSymbol, maxAge: 300.0) { if let priceData = priceDataAny as? SimplePriceOracle.PriceData { currentPrice = priceData.price if let triggerPrice = plan.triggerPrice { if plan.triggerDirection == DCAVaultActions.TriggerDirection.buyAbove { assert(currentPrice! >= triggerPrice, message: "Price condition not met") } else if plan.triggerDirection == DCAVaultActions.TriggerDirection.sellBelow { assert(currentPrice! <= triggerPrice, message: "Price condition not met") } } } else { panic("Invalid price data format") } } else { panic("Unable to fetch current price for ".concat(targetSymbol)) } } // Create swapper based on type from plan with COA capability let swapper <- self.createSwapper(plan, coaCap: executionData.coaCap) // Calculate amount after protocol fee (0.1%) let protocolFeePercent = DCAVaultActions.getProtocolFeePercent() let feeAmount = plan.purchaseAmount * protocolFeePercent let amountAfterFee = plan.purchaseAmount - feeAmount // Get quote for the swap let quote = swapper.getQuote( fromTokenType: plan.tokenPair.sourceTokenType, toTokenType: plan.tokenPair.targetTokenType, amount: amountAfterFee ) // Execute the purchase through vault vault.executePurchaseWithSwapper( planId: executionData.planId, swapper: <-swapper, quote: quote, currentPrice: currentPrice ) emit DCATransactionExecuted( planId: executionData.planId, scheduledTxId: id, amountSpent: plan.purchaseAmount, amountReceived: quote.expectedAmount ) // Check if EVM swap if self.isEVMSwapper(plan.swapperType) { emit EVMSwapExecuted( planId: executionData.planId, dexType: plan.swapperType, amountIn: plan.purchaseAmount, amountOut: quote.expectedAmount ) } // Re-schedule next execution if this is a recurring DCA and should continue if executionData.shouldContinue() && plan.purchasesExecuted < plan.totalPurchases { self.rescheduleNext(executionData) } else { emit DCASeriesCompleted( planId: executionData.planId, totalExecutions: executionData.count + 1 ) } } /// Create swapper for the specified DEX /// V2: Uses COA-based connectors for EVM swaps access(self) fun createSwapper( _ plan: DCAVaultActions.DCAPlan, coaCap: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>? ): @{DeFiActions.Swapper} { pre { plan.swapperType.length > 0: "Swapper type cannot be empty" } // Handle Cadence DEXes (IncrementFi) if plan.swapperType == "IncrementFi" || plan.swapperType == "increment" { let routerAddress = self.getContractAddress(Type<SwapRouter>()) let pairAddress = self.getContractAddress(Type<SwapFactory>()) let sourceTypeId = self.getContractTypeId(plan.tokenPair.sourceTokenType) let targetTypeId = self.getContractTypeId(plan.tokenPair.targetTokenType) return <- IncrementFiSwapperConnector.createSwapper( pairAddress: pairAddress, routerAddress: routerAddress, tokenKeyPath: [sourceTypeId, targetTypeId] ) } // Handle UniswapV2 EVM swapper (includes PunchSwap V2) // Uses V2 connector with COA support if plan.swapperType == "UniswapV2" || plan.swapperType == "uniswap-v2" || plan.swapperType == "punchswap-v2" { return <- self.createUniswapV2Swapper(plan, coaCap: coaCap) } // Handle UniswapV3 EVM swapper (includes FlowSwap V3, PunchSwap V3) // Uses V2 connector with COA support if plan.swapperType == "UniswapV3" || plan.swapperType == "uniswap-v3" || plan.swapperType == "flowswap-v3" || plan.swapperType == "punchswap-v3" { return <- self.createUniswapV3Swapper(plan, coaCap: coaCap) } panic("Unsupported swapper type: ".concat(plan.swapperType)) } /// Create Uniswap V2 swapper with COA for EVM execution access(self) fun createUniswapV2Swapper( _ plan: DCAVaultActions.DCAPlan, coaCap: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>? ): @{DeFiActions.Swapper} { let sourceTypeId = self.getContractTypeId(plan.tokenPair.sourceTokenType) let targetTypeId = self.getContractTypeId(plan.tokenPair.targetTokenType) let sourceEVMAddress = self.getEVMTokenAddress(plan.tokenPair.sourceTokenType) let targetEVMAddress = self.getEVMTokenAddress(plan.tokenPair.targetTokenType) let tokenPath: [EVM.EVMAddress] = [sourceEVMAddress, targetEVMAddress] return <- UniswapV2SwapperConnectorV2.createSwapperWithDefaults( tokenPath: tokenPath, cadenceTokenPath: [sourceTypeId, targetTypeId], coaCapability: coaCap, inVaultType: plan.tokenPair.sourceTokenType, outVaultType: plan.tokenPair.targetTokenType ) } /// Create Uniswap V3 swapper with COA for EVM execution access(self) fun createUniswapV3Swapper( _ plan: DCAVaultActions.DCAPlan, coaCap: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>? ): @{DeFiActions.Swapper} { let sourceTypeId = self.getContractTypeId(plan.tokenPair.sourceTokenType) let targetTypeId = self.getContractTypeId(plan.tokenPair.targetTokenType) let tokenIn = self.getEVMTokenAddress(plan.tokenPair.sourceTokenType) let tokenOut = self.getEVMTokenAddress(plan.tokenPair.targetTokenType) return <- UniswapV3SwapperConnectorV2.createSwapperWithDefaults( tokenIn: tokenIn, tokenOut: tokenOut, cadenceTokenIn: sourceTypeId, cadenceTokenOut: targetTypeId, coaCapability: coaCap, inVaultType: plan.tokenPair.sourceTokenType, outVaultType: plan.tokenPair.targetTokenType ) } /// Check if a swapper type is an EVM swapper access(self) fun isEVMSwapper(_ swapperType: String): Bool { return swapperType == "UniswapV2" || swapperType == "uniswap-v2" || swapperType == "UniswapV3" || swapperType == "uniswap-v3" || swapperType == "punchswap-v2" || swapperType == "punchswap-v3" || swapperType == "flowswap-v3" } /// Get EVM token address for a Cadence token type access(self) fun getEVMTokenAddress(_ tokenType: Type): EVM.EVMAddress { if let evmAddress = EVMTokenRegistry.getEVMAddress(tokenType) { return evmAddress } if tokenType == Type<@FlowToken.Vault>() { return EVMTokenRegistry.getFlowEVMAddress() } panic("No EVM address found for token type: ".concat(tokenType.identifier)) } /// Extract contract address from Type identifier access(self) fun getContractAddress(_ contractType: Type): Address { let typeId = contractType.identifier if typeId.length > 2 && typeId.slice(from: 0, upTo: 2) == "A." { let parts = typeId.split(separator: ".") if parts.length >= 2 { let addrHex = parts[1] return Address.fromString("0x".concat(addrHex)) ?? Address(0x0) } } return Address(0x0) } /// Extract contract type identifier (strip resource name) access(self) fun getContractTypeId(_ vaultType: Type): String { let typeId = vaultType.identifier let parts = typeId.split(separator: ".") if parts.length >= 4 && parts[parts.length - 1] == "Vault" { return "A.".concat(parts[1]).concat(".").concat(parts[2]) } return typeId } /// Re-schedule next DCA purchase access(self) fun rescheduleNext(_ data: ExecutionData) { pre { data.executionEffort >= 10: "Execution effort must be at least 10" } let nextTime = data.getNextExecutionTime() assert(nextTime > getCurrentBlock().timestamp, message: "Next execution time must be in the future") let updatedData = data.withIncrementedCount() let estimation = FlowTransactionScheduler.estimate( data: updatedData as AnyStruct, timestamp: nextTime, priority: data.priority, executionEffort: data.executionEffort ) let estimatedFee = estimation.flowFee ?? 0.01 assert(estimatedFee > 0.0, message: "Estimated fee must be positive") let feeProvider = data.feeCap.borrow() ?? panic("Fee provider capability is invalid") let feesVault <- feeProvider.withdraw(amount: estimatedFee) let fees <- feesVault as! @FlowToken.Vault let manager = data.mgrCap.borrow() ?? panic("Manager capability is invalid") let scheduledId = manager.schedule( handlerCap: data.handlerCap, data: updatedData, timestamp: nextTime, priority: data.priority, executionEffort: data.executionEffort, fees: <-fees ) emit DCATransactionScheduled( planId: data.planId, executionTime: nextTime, priority: data.priority.rawValue.toString() ) } /// Get views for metadata access(all) view fun getViews(): [Type] { return [Type<StoragePath>(), Type<PublicPath>()] } /// Resolve view access(all) fun resolveView(_ view: Type): AnyStruct? { switch view { case Type<StoragePath>(): return DCATransactionHandlerV2.HandlerStoragePath case Type<PublicPath>(): return DCATransactionHandlerV2.HandlerPublicPath default: return nil } } } /// Create a new DCA transaction handler access(all) fun createHandler(): @Handler { return <- create Handler() } init() { self.HandlerStoragePath = /storage/DCATransactionHandlerV2 self.HandlerPublicPath = /public/DCATransactionHandlerV2 } }

Cadence Script

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