Smart Contract
DCATestHandler
A.ca7ee55e4fc3251a.DCATestHandler
1import FlowTransactionScheduler from 0xe467b9dd11fa00df
2import FlowTransactionSchedulerUtils from 0xe467b9dd11fa00df
3import FlowToken from 0x1654653399040a61
4import FungibleToken from 0xf233dcee88fe0abe
5
6/// DCATestHandler: Simple Cadence-only test handler for scheduled transactions
7///
8/// This is a minimal test handler to verify the scheduled transaction pattern
9/// works correctly BEFORE adding EVM complexity.
10///
11/// Pattern: Follows official scaffold (CounterLoopTransactionHandler)
12/// - LoopConfig carries capabilities in TransactionData
13/// - Handler uses Manager.scheduleByHandler() to reschedule
14/// - No EVM calls - just logs and events
15///
16access(all) contract DCATestHandler {
17
18 // ============================================================
19 // Events
20 // ============================================================
21
22 access(all) event HandlerCreated(uuid: UInt64)
23 access(all) event TestExecuted(executionId: UInt64, count: UInt64, timestamp: UFix64)
24 access(all) event NextScheduled(executionId: UInt64, scheduledId: UInt64, timestamp: UFix64)
25 access(all) event SchedulingFailed(executionId: UInt64, reason: String)
26 access(all) event TestCompleted(executionId: UInt64, totalExecutions: UInt64)
27
28 // ============================================================
29 // Storage Paths
30 // ============================================================
31
32 access(all) let HandlerStoragePath: StoragePath
33 access(all) let HandlerPublicPath: PublicPath
34
35 // ============================================================
36 // LoopConfig: Capabilities passed in TransactionData
37 // ============================================================
38
39 access(all) struct LoopConfig {
40 access(all) let schedulerManagerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>
41 access(all) let feeProviderCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>
42 access(all) let priority: FlowTransactionScheduler.Priority
43 access(all) let executionEffort: UInt64
44 access(all) let intervalSeconds: UFix64
45 access(all) let maxExecutions: UInt64
46
47 init(
48 schedulerManagerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>,
49 feeProviderCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>,
50 priority: FlowTransactionScheduler.Priority,
51 executionEffort: UInt64,
52 intervalSeconds: UFix64,
53 maxExecutions: UInt64
54 ) {
55 self.schedulerManagerCap = schedulerManagerCap
56 self.feeProviderCap = feeProviderCap
57 self.priority = priority
58 self.executionEffort = executionEffort
59 self.intervalSeconds = intervalSeconds
60 self.maxExecutions = maxExecutions
61 }
62 }
63
64 // ============================================================
65 // TransactionData
66 // ============================================================
67
68 access(all) struct TransactionData {
69 access(all) let executionId: UInt64
70 access(all) let currentCount: UInt64
71 access(all) let loopConfig: LoopConfig
72
73 init(executionId: UInt64, currentCount: UInt64, loopConfig: LoopConfig) {
74 self.executionId = executionId
75 self.currentCount = currentCount
76 self.loopConfig = loopConfig
77 }
78 }
79
80 // ============================================================
81 // Handler Resource
82 // ============================================================
83
84 access(all) resource Handler: FlowTransactionScheduler.TransactionHandler {
85
86 access(FlowTransactionScheduler.Execute)
87 fun executeTransaction(id: UInt64, data: AnyStruct?) {
88 let txData = data as? TransactionData
89 if txData == nil {
90 log("DCATestHandler: Invalid transaction data")
91 return
92 }
93
94 let executionId = txData!.executionId
95 let currentCount = txData!.currentCount
96 let loopConfig = txData!.loopConfig
97 let newCount = currentCount + 1
98
99 log("DCATestHandler: Executing #".concat(newCount.toString()).concat(" of ").concat(loopConfig.maxExecutions.toString()))
100
101 emit TestExecuted(
102 executionId: executionId,
103 count: newCount,
104 timestamp: getCurrentBlock().timestamp
105 )
106
107 // Check if we should continue
108 if newCount >= loopConfig.maxExecutions {
109 emit TestCompleted(executionId: executionId, totalExecutions: newCount)
110 log("DCATestHandler: Completed all executions")
111 return
112 }
113
114 // Schedule next execution
115 let nextTime = getCurrentBlock().timestamp + loopConfig.intervalSeconds
116 let nextTxData = TransactionData(
117 executionId: executionId,
118 currentCount: newCount,
119 loopConfig: loopConfig
120 )
121
122 // Estimate fees
123 let estimate = FlowTransactionScheduler.estimate(
124 data: nextTxData,
125 timestamp: nextTime,
126 priority: loopConfig.priority,
127 executionEffort: loopConfig.executionEffort
128 )
129
130 let feeAmount = estimate.flowFee ?? 0.001
131 let feeWithBuffer = feeAmount * 1.1
132
133 // Borrow fee vault
134 let feeVault = loopConfig.feeProviderCap.borrow()
135 if feeVault == nil {
136 emit SchedulingFailed(executionId: executionId, reason: "Could not borrow fee vault")
137 return
138 }
139
140 if feeVault!.balance < feeWithBuffer {
141 emit SchedulingFailed(executionId: executionId, reason: "Insufficient fees: ".concat(feeWithBuffer.toString()))
142 return
143 }
144
145 let fees <- feeVault!.withdraw(amount: feeWithBuffer)
146
147 // Borrow scheduler manager
148 let schedulerManager = loopConfig.schedulerManagerCap.borrow()
149 if schedulerManager == nil {
150 feeVault!.deposit(from: <-fees)
151 emit SchedulingFailed(executionId: executionId, reason: "Could not borrow scheduler manager")
152 return
153 }
154
155 // Schedule next using Manager.scheduleByHandler()
156 let scheduledId = schedulerManager!.scheduleByHandler(
157 handlerTypeIdentifier: self.getType().identifier,
158 handlerUUID: self.uuid,
159 data: nextTxData,
160 timestamp: nextTime,
161 priority: loopConfig.priority,
162 executionEffort: loopConfig.executionEffort,
163 fees: <-fees as! @FlowToken.Vault
164 )
165
166 if scheduledId == 0 {
167 emit SchedulingFailed(executionId: executionId, reason: "scheduleByHandler returned 0")
168 return
169 }
170
171 emit NextScheduled(executionId: executionId, scheduledId: scheduledId, timestamp: nextTime)
172 log("DCATestHandler: Scheduled next execution #".concat((newCount + 1).toString()).concat(" at ").concat(nextTime.toString()))
173 }
174
175 init() {
176 emit HandlerCreated(uuid: self.uuid)
177 }
178 }
179
180 // ============================================================
181 // Factory Functions
182 // ============================================================
183
184 access(all) fun createHandler(): @Handler {
185 return <- create Handler()
186 }
187
188 access(all) fun createLoopConfig(
189 schedulerManagerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>,
190 feeProviderCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>,
191 priority: FlowTransactionScheduler.Priority,
192 executionEffort: UInt64,
193 intervalSeconds: UFix64,
194 maxExecutions: UInt64
195 ): LoopConfig {
196 return LoopConfig(
197 schedulerManagerCap: schedulerManagerCap,
198 feeProviderCap: feeProviderCap,
199 priority: priority,
200 executionEffort: executionEffort,
201 intervalSeconds: intervalSeconds,
202 maxExecutions: maxExecutions
203 )
204 }
205
206 access(all) fun createTransactionData(
207 executionId: UInt64,
208 currentCount: UInt64,
209 loopConfig: LoopConfig
210 ): TransactionData {
211 return TransactionData(
212 executionId: executionId,
213 currentCount: currentCount,
214 loopConfig: loopConfig
215 )
216 }
217
218 // ============================================================
219 // Init
220 // ============================================================
221
222 init() {
223 self.HandlerStoragePath = /storage/DCATestHandler
224 self.HandlerPublicPath = /public/DCATestHandler
225 }
226}
227