Smart Contract

DCAHandlerEVMV3

A.ca7ee55e4fc3251a.DCAHandlerEVMV3

Valid From

135,721,089

Deployed

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

Dependents

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