Smart Contract
DCAHandlerEVMV2
A.ca7ee55e4fc3251a.DCAHandlerEVMV2
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