Smart Contract
PrizeVaultScheduler
A.262cf58c0b9fbcff.PrizeVaultScheduler
1/*
2PrizeVaultScheduler - Automated Prize Draw Scheduling
3
4Manages automated prize draws for all PrizeSavings pools using a single
5unified scheduler handler. The scheduler maintains a simple registry of active
6pools and always queries pool configuration directly from PrizeSavings.
7
8Key Features:
9- Single handler manages all pools
10- Pool configuration queried directly from source (PrizeSavings)
11- Auto-scheduling after each draw completes
12- Centralized fee management
13- Graceful error handling
14- Simple registry-based pool tracking
15*/
16
17import FlowTransactionScheduler from 0xe467b9dd11fa00df
18import FlowTransactionSchedulerUtils from 0xe467b9dd11fa00df
19import FungibleToken from 0xf233dcee88fe0abe
20import FlowToken from 0x1654653399040a61
21import PrizeSavings from 0x262cf58c0b9fbcff
22import ViewResolver from 0x1d7e57aa55817448
23
24access(all) contract PrizeVaultScheduler {
25
26 // ========================================
27 // Constants
28 // ========================================
29
30 // ========================================
31 // Storage Paths
32 // ========================================
33
34 access(all) let HandlerStoragePath: StoragePath
35 access(all) let HandlerPublicPath: PublicPath
36 access(all) let FeeVaultStoragePath: StoragePath
37
38 // ========================================
39 // Events
40 // ========================================
41
42 access(all) event SchedulerInitialized(handlerAddress: Address)
43 access(all) event PoolRegistered(poolID: UInt64)
44 access(all) event PoolUnregistered(poolID: UInt64)
45 access(all) event DrawScheduled(poolID: UInt64, drawType: String, executionTime: UFix64)
46 access(all) event DrawExecuted(poolID: UInt64, drawType: String, success: Bool, error: String?)
47 access(all) event FeesFunded(amount: UFix64, newBalance: UFix64)
48 access(all) event FeesWithdrawn(amount: UFix64, purpose: String)
49
50 // ========================================
51 // Draw Data Structure
52 // ========================================
53
54 access(all) enum DrawType: UInt8 {
55 access(all) case Start
56 access(all) case Complete
57 }
58
59 access(all) struct DrawData {
60 access(all) let poolID: UInt64
61 access(all) let drawType: DrawType
62
63 init(poolID: UInt64, drawType: DrawType) {
64 self.poolID = poolID
65 self.drawType = drawType
66 }
67
68 access(all) fun getDrawTypeName(): String {
69 switch self.drawType {
70 case DrawType.Start:
71 return "START"
72 case DrawType.Complete:
73 return "COMPLETE"
74 }
75 return "UNKNOWN"
76 }
77 }
78
79 // ========================================
80 // Handler Resource
81 // ========================================
82
83 access(all) resource Handler: FlowTransactionScheduler.TransactionHandler {
84 // Account address where PrizeSavings is deployed
85 access(self) let vaultModularAddress: Address
86
87 // Capabilities for fee management and scheduling
88 access(self) let feeWithdrawCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>
89 access(self) let handlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>
90 access(self) let managerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>
91
92 // Pool registry - tracks which pools are actively being scheduled
93 // If a poolID is not in this registry, scheduling will stop for that pool
94 access(self) let managedPools: {UInt64: Bool}
95
96 // Schedule tracking
97 access(self) let lastExecutionTime: {UInt64: UFix64}
98
99 init(
100 vaultModularAddress: Address,
101 feeWithdrawCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>,
102 handlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>,
103 managerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>
104 ) {
105 self.vaultModularAddress = vaultModularAddress
106 self.feeWithdrawCap = feeWithdrawCap
107 self.handlerCap = handlerCap
108 self.managerCap = managerCap
109 self.managedPools = {}
110 self.lastExecutionTime = {}
111 }
112
113 // ========================================
114 // ViewResolver Interface (required by TransactionHandler)
115 // ========================================
116
117 access(all) view fun getViews(): [Type] {
118 return [Type<PublicPath>()]
119 }
120
121 access(all) fun resolveView(_ view: Type): AnyStruct? {
122 if view == Type<PublicPath>() {
123 return PrizeVaultScheduler.HandlerPublicPath
124 }
125 return nil
126 }
127
128 // ========================================
129 // FlowTransactionScheduler Interface
130 // ========================================
131
132 access(FlowTransactionScheduler.Execute) fun executeTransaction(id: UInt64, data: AnyStruct?) {
133 let drawData = data as! DrawData
134 let poolID = drawData.poolID
135
136 // Verify pool is in registry (actively being scheduled)
137 if self.managedPools[poolID] != true {
138 let error = "Pool ".concat(poolID.toString()).concat(" is not registered with scheduler - stopping scheduling")
139 return
140 }
141
142 let poolRef = PrizeSavings.borrowPool(poolID: poolID)
143 if poolRef == nil {
144 let error = "Cannot borrow pool ".concat(poolID.toString())
145 emit DrawExecuted(poolID: poolID, drawType: drawData.getDrawTypeName(), success: false, error: error)
146 return
147 }
148
149 // Execute based on draw type
150 var success = false
151 var errorMessage: String? = nil
152
153 switch drawData.drawType {
154 case DrawType.Start:
155 // Execute startDraw
156 poolRef!.startDraw()
157 success = true
158
159 // Schedule completeDraw for next block (~1 second later)
160 self.scheduleCompleteDraw(poolID: poolID)
161
162 case DrawType.Complete:
163 // Execute completeDraw
164 poolRef!.completeDraw()
165 success = true
166
167 // Schedule next draw based on pool's configuration
168 self.scheduleNextDraw(poolID: poolID)
169 }
170
171 // Update last execution time
172 self.lastExecutionTime[poolID] = getCurrentBlock().timestamp
173
174 emit DrawExecuted(
175 poolID: poolID,
176 drawType: drawData.getDrawTypeName(),
177 success: success,
178 error: errorMessage
179 )
180 }
181
182 // ========================================
183 // Pool Management
184 // ========================================
185
186 /// Register a pool for scheduling
187 /// This adds the pool to the active registry and automatically schedules the next draw
188 /// All pool configuration will be queried directly from PrizeSavings
189 access(all) fun registerPool(poolID: UInt64) {
190 pre {
191 self.managedPools[poolID] != true: "Pool already registered"
192 }
193
194 // Verify pool exists
195 let poolRef = PrizeSavings.borrowPool(poolID: poolID)
196 ?? panic("Pool does not exist: ".concat(poolID.toString()))
197
198 // Add to registry
199 self.managedPools[poolID] = true
200
201 emit PoolRegistered(poolID: poolID)
202
203 // Automatically schedule the next draw based on pool configuration
204 self.scheduleNextDraw(poolID: poolID)
205 }
206
207 /// Unregister a pool from scheduling
208 /// Removes the pool from the registry - future scheduled draws will not execute
209 access(all) fun unregisterPool(poolID: UInt64) {
210 pre {
211 self.managedPools[poolID] == true: "Pool not registered"
212 }
213
214 self.managedPools.remove(key: poolID)
215 self.lastExecutionTime.remove(key: poolID)
216
217 emit PoolUnregistered(poolID: poolID)
218 }
219
220 // ========================================
221 // Scheduling Functions
222 // ========================================
223
224 /// Schedule the next draw for a registered pool
225 /// Calculates timing based on pool's drawIntervalSeconds and lastDrawTimestamp
226 /// Queries all configuration directly from PrizeSavings
227 /// This is called automatically by registerPool() and after completeDraw()
228 access(self) fun scheduleNextDraw(poolID: UInt64) {
229 // Verify pool is still in registry
230 if self.managedPools[poolID] != true {
231 return
232 }
233
234 // Get pool reference and read its state
235 let poolRef = PrizeSavings.borrowPool(poolID: poolID)
236 if poolRef == nil {
237 return
238 }
239
240 let config = poolRef!.getConfig()
241 let lastDrawTimestamp = poolRef!.lastDrawTimestamp
242 let currentTimestamp = getCurrentBlock().timestamp
243 let drawIntervalSeconds = config.drawIntervalSeconds
244
245 // Calculate delay in seconds based on pool configuration
246 let delaySeconds = self.calculateDelayFromPoolConfig(
247 lastDrawTimestamp: lastDrawTimestamp,
248 currentTimestamp: currentTimestamp,
249 drawIntervalSeconds: drawIntervalSeconds
250 )
251
252 let executionTime = getCurrentBlock().timestamp + delaySeconds
253
254 let drawData = DrawData(
255 poolID: poolID,
256 drawType: DrawType.Start
257 )
258
259 self.scheduleTransaction(
260 data: drawData,
261 executionTime: executionTime
262 )
263 }
264
265 /// Helper function to calculate delay seconds from pool configuration
266 access(self) fun calculateDelayFromPoolConfig(
267 lastDrawTimestamp: UFix64,
268 currentTimestamp: UFix64,
269 drawIntervalSeconds: UFix64
270 ): UFix64 {
271 // Pool has never had a draw - schedule immediately (minimum delay)
272 if lastDrawTimestamp == 0.0 {
273 return 1.0
274 }
275
276 let timeSinceLastDraw = currentTimestamp - lastDrawTimestamp
277
278 // We're overdue - schedule immediately
279 if timeSinceLastDraw >= drawIntervalSeconds {
280 return 1.0
281 }
282
283 // Calculate time remaining until next scheduled draw
284 let timeUntilNextDraw = drawIntervalSeconds - timeSinceLastDraw
285 return timeUntilNextDraw
286 }
287
288 access(self) fun scheduleCompleteDraw(poolID: UInt64) {
289 // Schedule for next block (approximately 1 second)
290 let executionTime = getCurrentBlock().timestamp + 1.0
291
292 let drawData = DrawData(
293 poolID: poolID,
294 drawType: DrawType.Complete
295 )
296
297 self.scheduleTransaction(
298 data: drawData,
299 executionTime: executionTime
300 )
301 }
302
303 access(self) fun scheduleTransaction(data: DrawData, executionTime: UFix64) {
304 // Get manager
305 let manager = self.managerCap.borrow()
306 ?? panic("Cannot borrow manager")
307
308 // Estimate fees
309 let feeEstimate = FlowTransactionScheduler.estimate(
310 data: data,
311 timestamp: executionTime,
312 priority: FlowTransactionScheduler.Priority.Medium,
313 executionEffort: 2000
314 )
315
316 let feeAmount = feeEstimate.flowFee ?? 0.01
317
318 // Withdraw fees from capability
319 let feeVault = self.feeWithdrawCap.borrow()
320 ?? panic("Cannot borrow fee vault")
321
322 let fees <- feeVault.withdraw(amount: feeAmount) as! @FlowToken.Vault
323
324 emit FeesWithdrawn(amount: feeAmount, purpose: "Scheduled draw for pool ".concat(data.poolID.toString()))
325
326 // Schedule the transaction
327 manager.schedule(
328 handlerCap: self.handlerCap,
329 data: data,
330 timestamp: executionTime,
331 priority: FlowTransactionScheduler.Priority.Medium,
332 executionEffort: 2000,
333 fees: <-fees
334 )
335
336 emit DrawScheduled(
337 poolID: data.poolID,
338 drawType: data.getDrawTypeName(),
339 executionTime: executionTime
340 )
341 }
342
343 // ========================================
344 // Getters
345 // ========================================
346
347 access(all) fun isPoolRegistered(poolID: UInt64): Bool {
348 return self.managedPools[poolID] == true
349 }
350
351 access(all) fun getAllManagedPools(): [UInt64] {
352 return self.managedPools.keys
353 }
354
355 access(all) fun getLastExecutionTime(poolID: UInt64): UFix64? {
356 return self.lastExecutionTime[poolID]
357 }
358
359 access(all) fun getFeeBalance(): UFix64 {
360 if let vault = self.feeWithdrawCap.borrow() {
361 return vault.balance
362 }
363 return 0.0
364 }
365
366 /// Get the draw interval directly from the pool's configuration
367 access(all) fun getPoolDrawInterval(poolID: UInt64): UFix64? {
368 if let poolRef = PrizeSavings.borrowPool(poolID: poolID) {
369 let config = poolRef.getConfig()
370 return config.drawIntervalSeconds
371 }
372 return nil
373 }
374
375 /// Get the last draw timestamp directly from the pool
376 access(all) fun getPoolLastDrawTime(poolID: UInt64): UFix64? {
377 if let poolRef = PrizeSavings.borrowPool(poolID: poolID) {
378 return poolRef.lastDrawTimestamp
379 }
380 return nil
381 }
382 }
383
384 // ========================================
385 // Contract Functions
386 // ========================================
387
388 access(all) fun createHandler(
389 vaultModularAddress: Address,
390 feeWithdrawCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>,
391 handlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>,
392 managerCap: Capability<auth(FlowTransactionSchedulerUtils.Owner) &{FlowTransactionSchedulerUtils.Manager}>
393 ): @Handler {
394 return <- create Handler(
395 vaultModularAddress: vaultModularAddress,
396 feeWithdrawCap: feeWithdrawCap,
397 handlerCap: handlerCap,
398 managerCap: managerCap
399 )
400 }
401
402 // ========================================
403 // Initialization
404 // ========================================
405
406 init() {
407 self.HandlerStoragePath = /storage/PrizeVaultSchedulerHandler
408 self.HandlerPublicPath = /public/PrizeVaultSchedulerHandler
409 self.FeeVaultStoragePath = /storage/PrizeVaultSchedulerFeeVault
410 }
411}
412
413