Smart Contract

PrizeVaultScheduler

A.a092c4aab33daeda.PrizeVaultScheduler

Valid From

138,375,954

Deployed

1w ago
Feb 15, 2026, 11:01:02 PM UTC

Dependents

1 imports
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 0xa092c4aab33daeda
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