Smart Contract

PrizeWinnerTracker

A.a092c4aab33daeda.PrizeWinnerTracker

Valid From

138,375,773

Deployed

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

Dependents

4 imports
1/*
2PrizeWinnerTracker - Pluggable Winner History Tracking
3
4A separate contract that can be optionally plugged into PrizeVaultModular
5to track lottery winners without coupling the core logic.
6
7Design Principles:
8- Interface-based (can swap implementations)
9- Ring buffer (fixed size, efficient memory)
10- Read-only public access
11- Optional (PrizeVault works without it)
12*/
13
14access(all) contract PrizeWinnerTracker {
15    
16    // ========================================
17    // Storage Paths
18    // ========================================
19    
20    access(all) let TrackerStoragePath: StoragePath
21    access(all) let TrackerPublicPath: PublicPath
22    
23    // ========================================
24    // Events
25    // ========================================
26    
27    access(all) event WinnerRecorded(
28        poolID: UInt64,
29        round: UInt64,
30        winnerReceiverID: UInt64,
31        winnerAddress: Address,
32        amount: UFix64,
33        nftIDs: [UInt64],
34        timestamp: UFix64,
35        blockHeight: UInt64
36    )
37    
38    // ========================================
39    // Winner Record Structure
40    // ========================================
41    
42    access(all) struct WinnerRecord {
43        access(all) let poolID: UInt64
44        access(all) let round: UInt64
45        access(all) let winnerReceiverID: UInt64
46        access(all) let winnerAddress: Address
47        access(all) let amount: UFix64
48        access(all) let nftIDs: [UInt64]
49        access(all) let timestamp: UFix64
50        access(all) let blockHeight: UInt64
51        
52        init(
53            poolID: UInt64,
54            round: UInt64,
55            winnerReceiverID: UInt64,
56            winnerAddress: Address,
57            amount: UFix64,
58            nftIDs: [UInt64],
59            timestamp: UFix64,
60            blockHeight: UInt64
61        ) {
62            self.poolID = poolID
63            self.round = round
64            self.winnerReceiverID = winnerReceiverID
65            self.winnerAddress = winnerAddress
66            self.amount = amount
67            self.nftIDs = nftIDs
68            self.timestamp = timestamp
69            self.blockHeight = blockHeight
70        }
71    }
72    
73    // ========================================
74    // Winner Tracker Interface
75    // ========================================
76    
77    /// Interface that any winner tracking implementation must follow
78    access(all) resource interface WinnerTrackerPublic {
79        access(all) fun recordWinner(
80            poolID: UInt64,
81            round: UInt64,
82            winnerReceiverID: UInt64,
83            winnerAddress: Address,
84            amount: UFix64,
85            nftIDs: [UInt64]
86        )
87        
88        access(all) fun getRecentWinners(poolID: UInt64, limit: Int): [WinnerRecord]
89        access(all) fun getAllRecentWinners(limit: Int): [WinnerRecord]
90        access(all) fun getWinnerCount(poolID: UInt64): Int
91        access(all) fun getTotalWinnerCount(): Int
92        access(all) fun getNFTWinnersCount(poolID: UInt64): Int
93        access(all) fun getAllNFTWinnersCount(): Int
94    }
95    
96    // ========================================
97    // Ring Buffer Implementation
98    // ========================================
99    
100    /// Efficient ring buffer that stores last N winners per pool
101    access(all) resource RingBufferTracker: WinnerTrackerPublic {
102        access(self) let maxSize: Int
103        access(self) let winnersByPool: {UInt64: [WinnerRecord]}
104        access(self) var allWinners: [WinnerRecord]  // Global recent winners
105        access(self) var nextIndex: Int  // For ring buffer rotation
106        
107        init(maxSize: Int) {
108            pre {
109                maxSize > 0 && maxSize <= 1000: "Max size must be between 1 and 1000"
110            }
111            self.maxSize = maxSize
112            self.winnersByPool = {}
113            self.allWinners = []
114            self.nextIndex = 0
115        }
116        
117        access(all) fun recordWinner(
118            poolID: UInt64,
119            round: UInt64,
120            winnerReceiverID: UInt64,
121            winnerAddress: Address,
122            amount: UFix64,
123            nftIDs: [UInt64]
124        ) {
125            let record = WinnerRecord(
126                poolID: poolID,
127                round: round,
128                winnerReceiverID: winnerReceiverID,
129                winnerAddress: winnerAddress,
130                amount: amount,
131                nftIDs: nftIDs,
132                timestamp: getCurrentBlock().timestamp,
133                blockHeight: getCurrentBlock().height
134            )
135            
136            // Add to pool-specific list
137            if self.winnersByPool[poolID] == nil {
138                self.winnersByPool[poolID] = []
139            }
140            
141            var poolWinners = self.winnersByPool[poolID]!
142            poolWinners.append(record)
143            
144            // Keep only last maxSize winners per pool
145            if poolWinners.length > self.maxSize {
146                let _ = poolWinners.remove(at: 0)
147            }
148            
149            self.winnersByPool[poolID] = poolWinners
150            
151            // Add to global list (ring buffer)
152            if self.allWinners.length < self.maxSize {
153                // Still filling up
154                self.allWinners.append(record)
155            } else {
156                // Replace oldest (ring buffer)
157                self.allWinners[self.nextIndex] = record
158                self.nextIndex = (self.nextIndex + 1) % self.maxSize
159            }
160            
161            emit WinnerRecorded(
162                poolID: poolID,
163                round: round,
164                winnerReceiverID: winnerReceiverID,
165                winnerAddress: winnerAddress,
166                amount: amount,
167                nftIDs: nftIDs,
168                timestamp: record.timestamp,
169                blockHeight: record.blockHeight
170            )
171        }
172        
173        access(all) fun getRecentWinners(poolID: UInt64, limit: Int): [WinnerRecord] {
174            let winners = self.winnersByPool[poolID] ?? []
175            
176            if limit <= 0 || limit >= winners.length {
177                return winners
178            }
179            
180            // Return last 'limit' winners
181            let startIndex = winners.length - limit
182            var result: [WinnerRecord] = []
183            
184            var i = startIndex
185            while i < winners.length {
186                result.append(winners[i])
187                i = i + 1
188            }
189            
190            return result
191        }
192        
193        access(all) fun getAllRecentWinners(limit: Int): [WinnerRecord] {
194            if limit <= 0 || limit >= self.allWinners.length {
195                return self.allWinners
196            }
197            
198            // Return last 'limit' winners
199            let startIndex = self.allWinners.length - limit
200            var result: [WinnerRecord] = []
201            
202            var i = startIndex
203            while i < self.allWinners.length {
204                result.append(self.allWinners[i])
205                i = i + 1
206            }
207            
208            return result
209        }
210        
211        access(all) fun getWinnerCount(poolID: UInt64): Int {
212            return (self.winnersByPool[poolID] ?? []).length
213        }
214        
215        access(all) fun getTotalWinnerCount(): Int {
216            return self.allWinners.length
217        }
218        
219        access(all) fun getNFTWinnersCount(poolID: UInt64): Int {
220            let winners = self.winnersByPool[poolID] ?? []
221            var count = 0
222            for winner in winners {
223                if winner.nftIDs.length > 0 {
224                    count = count + 1
225                }
226            }
227            return count
228        }
229        
230        access(all) fun getAllNFTWinnersCount(): Int {
231            var count = 0
232            for winner in self.allWinners {
233                if winner.nftIDs.length > 0 {
234                    count = count + 1
235                }
236            }
237            return count
238        }
239    }
240    
241    // ========================================
242    // Factory Functions
243    // ========================================
244    
245    access(all) fun createRingBufferTracker(maxSize: Int): @RingBufferTracker {
246        return <- create RingBufferTracker(maxSize: maxSize)
247    }
248    
249    // ========================================
250    // Helper Functions
251    // ========================================
252    
253    /// Get tracker from an account (if it exists)
254    access(all) fun borrowTracker(account: Address): &{WinnerTrackerPublic}? {
255        return getAccount(account)
256            .capabilities.borrow<&{WinnerTrackerPublic}>(
257                PrizeWinnerTracker.TrackerPublicPath
258            )
259    }
260    
261    // ========================================
262    // Initialization
263    // ========================================
264    
265    init() {
266        self.TrackerStoragePath = /storage/PrizeWinnerTracker
267        self.TrackerPublicPath = /public/PrizeWinnerTracker
268    }
269}
270
271