DeploySEALED

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

Transaction ID

Timestamp

Dec 10, 2025, 10:43:57 PM UTC
2mo ago

Block Height

135,596,796

Computation

0

Execution Fee

0.00638 FLOW

Transaction Summary

Deploy

Contract deployment

Contract deployment

Script Arguments

0nameString
DCATransactionHandlerV3
1codeString
import FlowTransactionScheduler from 0xe467b9dd11fa00df import FlowTransactionSchedulerUtils from 0xe467b9dd11fa00df import DCAControllerV3 from 0xca7ee55e4fc3251a import DCAPlanV3 from 0xca7ee55e4fc3251a import DeFiMath from 0xca7ee55e4fc3251a import FungibleToken from 0xf233dcee88fe0abe import FlowToken from 0x1654653399040a61 import EVM from 0xe467b9dd11fa00df import UniswapV3SwapperConnector from 0xca7ee55e4fc3251a import EVMTokenRegistry from 0xca7ee55e4fc3251a import DeFiActions from 0xca7ee55e4fc3251a /// DCATransactionHandler: Scheduled transaction handler for DCA execution (EVM DEX swaps) /// /// This contract implements the FlowTransactionScheduler.TransactionHandler interface /// to enable autonomous DCA plan execution via Forte Scheduled Transactions. /// /// Architecture: /// 1. User creates DCA plan with DCAController /// 2. Plan schedules execution via FlowTransactionScheduler /// 3. At scheduled time, scheduler calls this handler's executeTransaction() /// 4. Handler: /// - Validates plan is ready /// - Creates UniswapV3Swapper with COA capability /// - Executes swap on Flow EVM (FlowSwap V3 → PunchSwap V2 fallback) /// - Updates plan accounting /// - Reschedules next execution if plan is still active /// /// Educational Notes: /// - Implements FlowTransactionScheduler.TransactionHandler interface /// - Access control: executeTransaction is access(FlowTransactionScheduler.Execute) /// - Uses DeFiActions.Swapper pattern for protocol-agnostic swaps /// - COA-based EVM execution (no manual wallet approvals) /// - Stores metadata via getViews/resolveView for discoverability access(all) contract DCATransactionHandlerV3 { /// Configuration for scheduling next execution access(all) struct ScheduleConfig { access(all) let schedulerManagerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}> access(all) let priority: FlowTransactionScheduler.Priority access(all) let executionEffort: UInt64 init( schedulerManagerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>, priority: FlowTransactionScheduler.Priority, executionEffort: UInt64 ) { self.schedulerManagerCap = schedulerManagerCap self.priority = priority self.executionEffort = executionEffort } } /// Transaction data passed to handler access(all) struct DCATransactionData { access(all) let planId: UInt64 access(all) let scheduleConfig: ScheduleConfig init(planId: UInt64, scheduleConfig: ScheduleConfig) { self.planId = planId self.scheduleConfig = scheduleConfig } } /// Event emitted when handler starts execution access(all) event HandlerExecutionStarted( transactionId: UInt64, planId: UInt64, owner: Address, timestamp: UFix64 ) /// Event emitted when handler completes successfully access(all) event HandlerExecutionCompleted( transactionId: UInt64, planId: UInt64, owner: Address, amountIn: UFix64, amountOut: UFix64, nextExecutionScheduled: Bool, timestamp: UFix64 ) /// Event emitted when handler execution fails access(all) event HandlerExecutionFailed( transactionId: UInt64, planId: UInt64?, owner: Address?, reason: String, timestamp: UFix64 ) /// Event emitted when next execution scheduling fails access(all) event NextExecutionSchedulingFailed( planId: UInt64, owner: Address, reason: String, timestamp: UFix64 ) /// Handler resource that implements the Scheduled Transaction interface /// /// Each user has one instance of this stored in their account. /// The scheduler calls executeTransaction() when a DCA plan is due. access(all) resource Handler: FlowTransactionScheduler.TransactionHandler { /// Reference to the user's DCA controller /// This capability allows the handler to access and update plans access(self) let controllerCap: Capability<auth(DCAControllerV3.Owner) &DCAControllerV3.Controller> init(controllerCap: Capability<auth(DCAControllerV3.Owner) &DCAControllerV3.Controller>) { pre { controllerCap.check(): "Invalid controller capability" } self.controllerCap = controllerCap } /// Main execution entrypoint called by FlowTransactionScheduler /// /// @param id: Transaction ID from the scheduler /// @param data: Encoded plan data (contains planId) /// /// This function has restricted access - only FlowTransactionScheduler can call it access(FlowTransactionScheduler.Execute) fun executeTransaction(id: UInt64, data: AnyStruct?) { let timestamp = getCurrentBlock().timestamp // Parse transaction data let txData = data as! DCATransactionData? ?? panic("Invalid transaction data format") let planId = txData.planId let scheduleConfig = txData.scheduleConfig let ownerAddress = self.controllerCap.address emit HandlerExecutionStarted( transactionId: id, planId: planId, owner: ownerAddress, timestamp: timestamp ) // Borrow controller let controller = self.controllerCap.borrow() ?? panic("Could not borrow DCA controller") // Borrow plan let planRef = controller.borrowPlan(id: planId) ?? panic("Could not borrow plan with ID: ".concat(planId.toString())) // Validate plan is ready if !planRef.isReadyForExecution() { emit HandlerExecutionFailed( transactionId: id, planId: planId, owner: ownerAddress, reason: "Plan not ready for execution", timestamp: timestamp ) return } // Check max executions if planRef.hasReachedMaxExecutions() { emit HandlerExecutionFailed( transactionId: id, planId: planId, owner: ownerAddress, reason: "Plan has reached maximum executions", timestamp: timestamp ) return } // Get vault capabilities let sourceVaultCap = controller.getSourceVaultCapability() ?? panic("Source vault capability not configured") let targetVaultCap = controller.getTargetVaultCapability() ?? panic("Target vault capability not configured") let coaCap = controller.getCOACapability() ?? panic("COA capability not configured") // Validate capabilities if !sourceVaultCap.check() { emit HandlerExecutionFailed( transactionId: id, planId: planId, owner: ownerAddress, reason: "Invalid source vault capability", timestamp: timestamp ) return } if !targetVaultCap.check() { emit HandlerExecutionFailed( transactionId: id, planId: planId, owner: ownerAddress, reason: "Invalid target vault capability", timestamp: timestamp ) return } if !coaCap.check() { emit HandlerExecutionFailed( transactionId: id, planId: planId, owner: ownerAddress, reason: "Invalid COA capability", timestamp: timestamp ) return } // Execute the swap let result = self.executeSwap( planRef: planRef, sourceVaultCap: sourceVaultCap, targetVaultCap: targetVaultCap, coaCap: coaCap ) if result.success { // Record successful execution planRef.recordExecution( amountIn: result.amountIn!, amountOut: result.amountOut! ) // Schedule next execution if plan is still active var nextScheduled = false if planRef.status == DCAPlanV3.PlanStatus.Active && !planRef.hasReachedMaxExecutions() { planRef.scheduleNextExecution() // Attempt to schedule next execution via Manager let schedulingResult = self.scheduleNextExecution( planId: planId, nextExecutionTime: planRef.nextExecutionTime, scheduleConfig: scheduleConfig ) nextScheduled = schedulingResult } emit HandlerExecutionCompleted( transactionId: id, planId: planId, owner: ownerAddress, amountIn: result.amountIn!, amountOut: result.amountOut!, nextExecutionScheduled: nextScheduled, timestamp: timestamp ) } else { emit HandlerExecutionFailed( transactionId: id, planId: planId, owner: ownerAddress, reason: result.errorMessage ?? "Unknown error", timestamp: timestamp ) } } /// Execute swap using UniswapV3SwapperConnector on Flow EVM /// /// This uses production EVM DEX infrastructure (FlowSwap V3 → PunchSwap V2 fallback) /// with COA-based execution and FlowEVMBridge for token conversion. /// /// @param planRef: Reference to the DCA plan /// @param sourceVaultCap: Capability to withdraw from source vault /// @param targetVaultCap: Capability to deposit to target vault /// @param coaCap: COA capability for EVM operations /// @return ExecutionResult with success status and amounts access(self) fun executeSwap( planRef: &DCAPlanV3.Plan, sourceVaultCap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>, targetVaultCap: Capability<&{FungibleToken.Receiver}>, coaCap: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount> ): ExecutionResult { // Get amount to invest let amountIn = planRef.amountPerInterval // Borrow source vault and check balance let sourceVault = sourceVaultCap.borrow() ?? panic("Could not borrow source vault") if sourceVault.balance < amountIn { return ExecutionResult( success: false, amountIn: nil, amountOut: nil, errorMessage: "Insufficient balance in source vault. Required: ".concat(amountIn.toString()).concat(", Available: ").concat(sourceVault.balance.toString()) ) } // Get EVM addresses for source and target tokens let sourceEVMAddr = EVMTokenRegistry.getEVMAddress(planRef.sourceTokenType) if sourceEVMAddr == nil { return ExecutionResult( success: false, amountIn: nil, amountOut: nil, errorMessage: "Source token not registered in EVMTokenRegistry: ".concat(planRef.sourceTokenType.identifier) ) } let targetEVMAddr = EVMTokenRegistry.getEVMAddress(planRef.targetTokenType) if targetEVMAddr == nil { return ExecutionResult( success: false, amountIn: nil, amountOut: nil, errorMessage: "Target token not registered in EVMTokenRegistry: ".concat(planRef.targetTokenType.identifier) ) } // Create EVM token path (source → target) let evmTokenPath: [EVM.EVMAddress] = [sourceEVMAddr!, targetEVMAddr!] // Use 3000 (0.3%) fee tier for most pairs (standard UniswapV3 fee) let feePath: [UInt32] = [3000] // Create swapper with defaults (FlowSwap V3 router) let swapper <- UniswapV3SwapperConnector.createSwapperWithDefaults( tokenPath: evmTokenPath, feePath: feePath, inVaultType: planRef.sourceTokenType, outVaultType: planRef.targetTokenType, coaCapability: coaCap ) // Get quote from swapper let quote = swapper.getQuote( fromTokenType: planRef.sourceTokenType, toTokenType: planRef.targetTokenType, amount: amountIn ) // Apply user's slippage tolerance to quote // maxSlippageBps is in basis points (100 = 1%) let slippageMultiplier = UInt64(10000) - planRef.maxSlippageBps let minAmountOut = quote.expectedAmount * UFix64(slippageMultiplier) / 10000.0 // Create adjusted quote with user's slippage protection let adjustedQuote = DeFiActions.Quote( expectedAmount: quote.expectedAmount, minAmount: minAmountOut, slippageTolerance: UFix64(planRef.maxSlippageBps) / 10000.0, deadline: getCurrentBlock().timestamp + 300.0, // 5 minutes data: quote.data ) // Withdraw tokens to swap let tokensToSwap <- sourceVault.withdraw(amount: amountIn) // Execute swap let swappedTokens <- swapper.swap( inVault: <-tokensToSwap, quote: adjustedQuote ) // Clean up swapper resource destroy swapper // Get actual output amount let actualAmountOut = swappedTokens.balance // Deposit to target vault let targetVault = targetVaultCap.borrow() ?? panic("Could not borrow target vault") targetVault.deposit(from: <-swappedTokens) return ExecutionResult( success: true, amountIn: amountIn, amountOut: actualAmountOut, errorMessage: nil ) } /// Schedule the next execution via FlowTransactionScheduler Manager /// /// This function handles the recursive scheduling pattern using the Manager approach: /// 1. Extract ScheduleConfig from transaction data (contains Manager capability) /// 2. Estimate fees for next execution /// 3. Withdraw FLOW fees from user's controller /// 4. Call manager.scheduleByHandler() to schedule next execution /// /// @param planId: Plan ID to include in transaction data /// @param nextExecutionTime: Timestamp for next execution /// @param scheduleConfig: Configuration containing Manager capability /// @return Bool indicating if scheduling succeeded access(self) fun scheduleNextExecution( planId: UInt64, nextExecutionTime: UFix64?, scheduleConfig: ScheduleConfig ): Bool { let ownerAddress = self.controllerCap.address let timestamp = getCurrentBlock().timestamp // Verify nextExecutionTime is provided if nextExecutionTime == nil { emit NextExecutionSchedulingFailed( planId: planId, owner: ownerAddress, reason: "Next execution time not set", timestamp: timestamp ) return false } // Prepare transaction data with plan ID and schedule config let transactionData = DCATransactionData( planId: planId, scheduleConfig: scheduleConfig ) // Estimate fees for the next execution let estimate = FlowTransactionScheduler.estimate( data: transactionData, timestamp: nextExecutionTime!, priority: scheduleConfig.priority, executionEffort: scheduleConfig.executionEffort ) // Check if estimation was successful assert( estimate.timestamp != nil || scheduleConfig.priority == FlowTransactionScheduler.Priority.Low, message: estimate.error ?? "Fee estimation failed" ) // Borrow the controller to access fee vault let controller = self.controllerCap.borrow() if controller == nil { emit NextExecutionSchedulingFailed( planId: planId, owner: ownerAddress, reason: "Could not borrow controller", timestamp: timestamp ) return false } // Get fee vault capability from controller let feeVaultCap = controller!.getFeeVaultCapability() if feeVaultCap == nil || !feeVaultCap!.check() { emit NextExecutionSchedulingFailed( planId: planId, owner: ownerAddress, reason: "Fee vault capability invalid or not set", timestamp: timestamp ) return false } // Withdraw fees let feeVault = feeVaultCap!.borrow() if feeVault == nil { emit NextExecutionSchedulingFailed( planId: planId, owner: ownerAddress, reason: "Could not borrow fee vault", timestamp: timestamp ) return false } let feeAmount = estimate.flowFee ?? 0.0 if feeVault!.balance < feeAmount { emit NextExecutionSchedulingFailed( planId: planId, owner: ownerAddress, reason: "Insufficient FLOW for fees. Required: ".concat(feeAmount.toString()).concat(", Available: ").concat(feeVault!.balance.toString()), timestamp: timestamp ) return false } let fees <- feeVault!.withdraw(amount: feeAmount) // Borrow scheduler manager let schedulerManager = scheduleConfig.schedulerManagerCap.borrow() if schedulerManager == nil { destroy fees emit NextExecutionSchedulingFailed( planId: planId, owner: ownerAddress, reason: "Could not borrow scheduler manager", timestamp: timestamp ) return false } // Use scheduleByHandler() on the Manager to schedule next execution // This works because the handler was previously scheduled through this Manager let scheduledId = schedulerManager!.scheduleByHandler( handlerTypeIdentifier: self.getType().identifier, handlerUUID: self.uuid, data: transactionData, timestamp: nextExecutionTime!, priority: scheduleConfig.priority, executionEffort: scheduleConfig.executionEffort, fees: <-fees as! @FlowToken.Vault ) // scheduledId > 0 means success if scheduledId == 0 { emit NextExecutionSchedulingFailed( planId: planId, owner: ownerAddress, reason: "Manager.scheduleByHandler() returned 0 (failed to schedule)", timestamp: timestamp ) return false } return true } /// Get supported view types (for resource metadata) access(all) view fun getViews(): [Type] { return [Type<StoragePath>(), Type<PublicPath>()] } /// Resolve a specific view type access(all) fun resolveView(_ view: Type): AnyStruct? { switch view { case Type<StoragePath>(): return /storage/DCATransactionHandlerV3 case Type<PublicPath>(): return /public/DCATransactionHandlerV3 default: return nil } } } /// Result struct for swap execution access(all) struct ExecutionResult { access(all) let success: Bool access(all) let amountIn: UFix64? access(all) let amountOut: UFix64? access(all) let errorMessage: String? init(success: Bool, amountIn: UFix64?, amountOut: UFix64?, errorMessage: String?) { self.success = success self.amountIn = amountIn self.amountOut = amountOut self.errorMessage = errorMessage } } /// Factory function to create a new handler /// /// @param controllerCap: Capability to the user's DCA controller /// @return New handler resource access(all) fun createHandler( controllerCap: Capability<auth(DCAControllerV3.Owner) &DCAControllerV3.Controller> ): @Handler { return <- create Handler(controllerCap: controllerCap) } /// Helper function to create schedule configuration access(all) fun createScheduleConfig( schedulerManagerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>, priority: FlowTransactionScheduler.Priority, executionEffort: UInt64 ): ScheduleConfig { return ScheduleConfig( schedulerManagerCap: schedulerManagerCap, priority: priority, executionEffort: executionEffort ) } /// Helper function to create transaction data access(all) fun createTransactionData( planId: UInt64, scheduleConfig: ScheduleConfig ): DCATransactionData { return DCATransactionData( planId: planId, scheduleConfig: scheduleConfig ) } }

Cadence Script

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