Smart Contract

DCAHandlerEVMV2

A.ca7ee55e4fc3251a.DCAHandlerEVMV2

Valid From

135,720,249

Deployed

5d ago
Feb 23, 2026, 12:47:19 AM UTC

Dependents

1 imports
1import FlowTransactionScheduler from 0xe467b9dd11fa00df
2import FlowTransactionSchedulerUtils from 0xe467b9dd11fa00df
3import FlowToken from 0x1654653399040a61
4import FungibleToken from 0xf233dcee88fe0abe
5import DCAServiceEVM from 0xca7ee55e4fc3251a
6
7/// DCAHandlerEVMV2: Autonomous Scheduled Transaction Handler for EVM-Native DCA
8///
9/// This is an enhanced version of DCAHandlerEVM that uses the Manager pattern
10/// for autonomous rescheduling. After each execution, it automatically schedules
11/// the next execution without requiring backend intervention.
12///
13/// Key features:
14/// - Works with existing DCAServiceEVM (no migration needed)
15/// - Uses FlowTransactionSchedulerUtils.Manager for auto-rescheduling
16/// - Withdraws fees from a configured fee vault capability
17///
18access(all) contract DCAHandlerEVMV2 {
19
20    // ============================================================
21    // Events
22    // ============================================================
23
24    access(all) event HandlerCreated(uuid: UInt64)
25    access(all) event ExecutionTriggered(planId: UInt64, success: Bool, nextScheduled: Bool)
26    access(all) event ExecutionSkipped(planId: UInt64, reason: String)
27    access(all) event NextExecutionScheduled(planId: UInt64, scheduledId: UInt64, timestamp: UFix64)
28    access(all) event NextExecutionSchedulingFailed(planId: UInt64, reason: String)
29
30    // ============================================================
31    // Storage Paths
32    // ============================================================
33
34    access(all) let HandlerStoragePath: StoragePath
35
36    // ============================================================
37    // Configuration Structs
38    // ============================================================
39
40    /// Configuration for autonomous rescheduling
41    access(all) struct ScheduleConfig {
42        access(all) let schedulerManagerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>
43        access(all) let feeVaultCap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>
44        access(all) let priority: FlowTransactionScheduler.Priority
45        access(all) let executionEffort: UInt64
46
47        init(
48            schedulerManagerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>,
49            feeVaultCap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>,
50            priority: FlowTransactionScheduler.Priority,
51            executionEffort: UInt64
52        ) {
53            self.schedulerManagerCap = schedulerManagerCap
54            self.feeVaultCap = feeVaultCap
55            self.priority = priority
56            self.executionEffort = executionEffort
57        }
58    }
59
60    /// Transaction data passed to handler (includes scheduling config)
61    access(all) struct TransactionData {
62        access(all) let planId: UInt64
63        access(all) let scheduleConfig: ScheduleConfig
64
65        init(planId: UInt64, scheduleConfig: ScheduleConfig) {
66            self.planId = planId
67            self.scheduleConfig = scheduleConfig
68        }
69    }
70
71    // ============================================================
72    // Handler Resource
73    // ============================================================
74
75    access(all) resource Handler: FlowTransactionScheduler.TransactionHandler {
76
77        /// Main execution entrypoint called by FlowTransactionScheduler
78        access(FlowTransactionScheduler.Execute)
79        fun executeTransaction(id: UInt64, data: AnyStruct?) {
80            // Parse transaction data
81            let txData = data as? TransactionData
82            if txData == nil {
83                log("DCAHandlerEVMV2: Invalid transaction data")
84                return
85            }
86
87            let planId = txData!.planId
88            let scheduleConfig = txData!.scheduleConfig
89
90            // Get plan details
91            let planOpt = DCAServiceEVM.getPlan(planId: planId)
92            if planOpt == nil {
93                emit ExecutionSkipped(planId: planId, reason: "Plan not found")
94                return
95            }
96            let plan = planOpt!
97
98            // Check if plan is active
99            if plan.getStatus() != DCAServiceEVM.PlanStatus.Active {
100                emit ExecutionSkipped(planId: planId, reason: "Plan not active")
101                return
102            }
103
104            // Execute the DCA plan via DCAServiceEVM
105            let success = DCAServiceEVM.executePlan(planId: planId)
106
107            // If successful and plan still active, schedule next execution
108            var nextScheduled = false
109            if success {
110                // Re-fetch plan to get updated nextExecutionTime
111                let updatedPlanOpt = DCAServiceEVM.getPlan(planId: planId)
112                if updatedPlanOpt != nil {
113                    let updatedPlan = updatedPlanOpt!
114
115                    // Only reschedule if plan is still active
116                    if updatedPlan.getStatus() == DCAServiceEVM.PlanStatus.Active {
117                        nextScheduled = self.scheduleNextExecution(
118                            planId: planId,
119                            nextExecutionTime: updatedPlan.nextExecutionTime,
120                            scheduleConfig: scheduleConfig
121                        )
122                    }
123                }
124            }
125
126            emit ExecutionTriggered(planId: planId, success: success, nextScheduled: nextScheduled)
127        }
128
129        /// Schedule the next execution via FlowTransactionScheduler Manager
130        access(self) fun scheduleNextExecution(
131            planId: UInt64,
132            nextExecutionTime: UFix64?,
133            scheduleConfig: ScheduleConfig
134        ): Bool {
135            // Verify nextExecutionTime is provided
136            if nextExecutionTime == nil {
137                emit NextExecutionSchedulingFailed(planId: planId, reason: "Next execution time not set")
138                return false
139            }
140
141            // Prepare transaction data for next execution
142            let nextTxData = TransactionData(
143                planId: planId,
144                scheduleConfig: scheduleConfig
145            )
146
147            // Estimate fees
148            let estimate = FlowTransactionScheduler.estimate(
149                data: nextTxData,
150                timestamp: nextExecutionTime!,
151                priority: scheduleConfig.priority,
152                executionEffort: scheduleConfig.executionEffort
153            )
154
155            let feeAmount = estimate.flowFee ?? 0.001 // Default to 0.001 if nil
156            let feeWithBuffer = feeAmount * 1.1 // Add 10% buffer
157
158            // Borrow fee vault
159            let feeVault = scheduleConfig.feeVaultCap.borrow()
160            if feeVault == nil {
161                emit NextExecutionSchedulingFailed(planId: planId, reason: "Could not borrow fee vault")
162                return false
163            }
164
165            // Check balance
166            if feeVault!.balance < feeWithBuffer {
167                emit NextExecutionSchedulingFailed(
168                    planId: planId,
169                    reason: "Insufficient fees. Required: ".concat(feeWithBuffer.toString()).concat(", Available: ").concat(feeVault!.balance.toString())
170                )
171                return false
172            }
173
174            // Withdraw fees
175            let fees <- feeVault!.withdraw(amount: feeWithBuffer)
176
177            // Borrow scheduler manager
178            let schedulerManager = scheduleConfig.schedulerManagerCap.borrow()
179            if schedulerManager == nil {
180                // Return fees to vault
181                feeVault!.deposit(from: <-fees)
182                emit NextExecutionSchedulingFailed(planId: planId, reason: "Could not borrow scheduler manager")
183                return false
184            }
185
186            // Schedule next execution using Manager.scheduleByHandler()
187            let scheduledId = schedulerManager!.scheduleByHandler(
188                handlerTypeIdentifier: self.getType().identifier,
189                handlerUUID: self.uuid,
190                data: nextTxData,
191                timestamp: nextExecutionTime!,
192                priority: scheduleConfig.priority,
193                executionEffort: scheduleConfig.executionEffort,
194                fees: <-fees as! @FlowToken.Vault
195            )
196
197            if scheduledId == 0 {
198                emit NextExecutionSchedulingFailed(planId: planId, reason: "scheduleByHandler returned 0")
199                return false
200            }
201
202            emit NextExecutionScheduled(planId: planId, scheduledId: scheduledId, timestamp: nextExecutionTime!)
203            return true
204        }
205
206        init() {
207            emit HandlerCreated(uuid: self.uuid)
208        }
209    }
210
211    // ============================================================
212    // Factory Functions
213    // ============================================================
214
215    /// Create a new handler resource
216    access(all) fun createHandler(): @Handler {
217        return <- create Handler()
218    }
219
220    /// Create schedule configuration
221    access(all) fun createScheduleConfig(
222        schedulerManagerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>,
223        feeVaultCap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>,
224        priority: FlowTransactionScheduler.Priority,
225        executionEffort: UInt64
226    ): ScheduleConfig {
227        return ScheduleConfig(
228            schedulerManagerCap: schedulerManagerCap,
229            feeVaultCap: feeVaultCap,
230            priority: priority,
231            executionEffort: executionEffort
232        )
233    }
234
235    /// Create transaction data
236    access(all) fun createTransactionData(
237        planId: UInt64,
238        scheduleConfig: ScheduleConfig
239    ): TransactionData {
240        return TransactionData(
241            planId: planId,
242            scheduleConfig: scheduleConfig
243        )
244    }
245
246    // ============================================================
247    // Init
248    // ============================================================
249
250    init() {
251        self.HandlerStoragePath = /storage/DCAHandlerEVMV2
252    }
253}
254