Smart Contract

PrizeWinnerTracker

A.6a3c3dad7074bca5.PrizeWinnerTracker

Valid From

136,896,040

Deployed

5d ago
Feb 23, 2026, 01:23:35 AM UTC

Dependents

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