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