Smart Contract

VRFExtension

A.79f5b5b0f95a160b.VRFExtension

Valid From

128,852,734

Deployed

1w ago
Feb 20, 2026, 08:45:43 AM UTC

Dependents

4 imports
1import VaultCore from 0x79f5b5b0f95a160b
2
3access(all) contract VRFExtension {
4    
5    access(all) let CADENCE_ARCH_VRF: Address
6    access(all) let BASIS_POINTS: UFix64
7    access(all) let ExtensionStoragePath: StoragePath
8    access(all) let ExtensionPublicPath: PublicPath
9    
10    access(all) event EpochAdvanced(epochNumber: UInt64, startTime: UFix64, yieldPool: UFix64)
11    access(all) event RewardClaimed(user: Address, epoch: UInt64, won: Bool, baseYield: UFix64, actualPayout: UFix64, multiplier: UFix64)
12    access(all) event VRFMultiplierSet(user: Address, multiplier: UFix64)
13    access(all) event YieldAdded(amount: UFix64, epoch: UInt64)
14    
15    access(self) var currentEpoch: UInt64
16    access(self) var epochDuration: UFix64
17    access(self) var lastEpochStart: UFix64
18    access(self) var epochsPerPayout: UInt64
19    access(self) var totalYieldPool: UFix64
20    access(self) var totalDistributed: UFix64
21    
22    access(all) struct VRFMultiplierOption {
23        access(all) let multiplier: UFix64
24        access(all) let probability: UFix64
25        
26        init(multiplier: UFix64, probability: UFix64) {
27            self.multiplier = multiplier
28            self.probability = probability
29        }
30    }
31    
32    access(all) struct EpochData {
33        access(all) let epochNumber: UInt64
34        access(all) let startTime: UFix64
35        access(all) var endTime: UFix64
36        access(all) var totalYieldPool: UFix64
37        access(all) var totalDistributed: UFix64
38        access(all) var participantCount: UInt64
39        access(all) var finalized: Bool
40        
41        init(epochNumber: UInt64, startTime: UFix64) {
42            self.epochNumber = epochNumber
43            self.startTime = startTime
44            self.endTime = startTime + VRFExtension.epochDuration
45            self.totalYieldPool = 0.0
46            self.totalDistributed = 0.0
47            self.participantCount = 0
48            self.finalized = false
49        }
50        
51        access(contract) fun finalize() {
52            self.finalized = true
53            self.endTime = getCurrentBlock().timestamp
54        }
55        
56        access(contract) fun addYield(amount: UFix64) {
57            self.totalYieldPool = self.totalYieldPool + amount
58        }
59        
60        access(contract) fun recordDistribution(amount: UFix64) {
61            self.totalDistributed = self.totalDistributed + amount
62        }
63        
64        access(contract) fun incrementParticipants() {
65            self.participantCount = self.participantCount + 1
66        }
67    }
68    
69    access(all) struct UserEpochData {
70        access(all) let user: Address
71        access(all) let epoch: UInt64
72        access(all) let vrfMultiplier: UFix64
73        access(all) let depositAmount: UFix64
74        access(all) let claimed: Bool
75        access(all) let won: Bool
76        access(all) let payoutAmount: UFix64
77        
78        init(user: Address, epoch: UInt64, vrfMultiplier: UFix64, depositAmount: UFix64, claimed: Bool, won: Bool, payoutAmount: UFix64) {
79            self.user = user
80            self.epoch = epoch
81            self.vrfMultiplier = vrfMultiplier
82            self.depositAmount = depositAmount
83            self.claimed = claimed
84            self.won = won
85            self.payoutAmount = payoutAmount
86        }
87    }
88    
89    access(all) resource Extension {
90        access(self) let vaultCap: Capability<&VaultCore.Vault>
91        access(self) let epochs: {UInt64: EpochData}
92        access(self) let userEpochData: {Address: {UInt64: UserEpochData}}
93        access(self) let vrfMultipliers: {UFix64: VRFMultiplierOption}
94        
95        init(vaultCap: Capability<&VaultCore.Vault>) {
96            self.vaultCap = vaultCap
97            self.epochs = {}
98            self.userEpochData = {}
99            self.vrfMultipliers = {}
100            
101            self.epochs[VRFExtension.currentEpoch] = EpochData(
102                epochNumber: VRFExtension.currentEpoch,
103                startTime: VRFExtension.lastEpochStart
104            )
105            
106            self.initializeVRFMultipliers()
107        }
108        
109        access(self) fun initializeVRFMultipliers() {
110            self.vrfMultipliers[1.0] = VRFMultiplierOption(multiplier: 1.0, probability: 10000.0)
111            self.vrfMultipliers[2.0] = VRFMultiplierOption(multiplier: 2.0, probability: 5000.0)
112            self.vrfMultipliers[5.0] = VRFMultiplierOption(multiplier: 5.0, probability: 2000.0)
113            self.vrfMultipliers[10.0] = VRFMultiplierOption(multiplier: 10.0, probability: 1000.0)
114            self.vrfMultipliers[50.0] = VRFMultiplierOption(multiplier: 50.0, probability: 200.0)
115            self.vrfMultipliers[100.0] = VRFMultiplierOption(multiplier: 100.0, probability: 100.0)
116        }
117        
118        access(all) fun advanceEpoch() {
119            self.epochs[VRFExtension.currentEpoch]!.finalize()
120            VRFExtension.currentEpoch = VRFExtension.currentEpoch + 1
121            VRFExtension.lastEpochStart = getCurrentBlock().timestamp
122            
123            self.epochs[VRFExtension.currentEpoch] = EpochData(
124                epochNumber: VRFExtension.currentEpoch,
125                startTime: VRFExtension.lastEpochStart
126            )
127            
128            emit EpochAdvanced(epochNumber: VRFExtension.currentEpoch, startTime: VRFExtension.lastEpochStart, yieldPool: VRFExtension.totalYieldPool)
129        }
130        
131        access(all) fun addYield(amount: UFix64) {
132            pre {
133                amount > 0.0: "Amount must be positive"
134            }
135            
136            VRFExtension.totalYieldPool = VRFExtension.totalYieldPool + amount
137            self.epochs[VRFExtension.currentEpoch]!.addYield(amount: amount)
138            emit YieldAdded(amount: amount, epoch: VRFExtension.currentEpoch)
139        }
140        
141        access(all) fun setUserVRFMultiplier(user: Address, multiplier: UFix64) {
142            pre {
143                self.vrfMultipliers[multiplier] != nil: "Invalid multiplier"
144            }
145            
146            let vault = self.vaultCap.borrow() ?? panic("Cannot borrow vault")
147            vault.setUserVRFMultiplier(user: user, multiplier: multiplier)
148            emit VRFMultiplierSet(user: user, multiplier: multiplier)
149        }
150        
151        access(all) fun recordUserDeposit(user: Address, amount: UFix64, vrfMultiplier: UFix64) {
152            let eligibilityEpoch = VRFExtension.currentEpoch + VRFExtension.epochsPerPayout
153            
154            if self.userEpochData[user] == nil {
155                self.userEpochData[user] = {}
156            }
157            
158            if self.userEpochData[user]![eligibilityEpoch] == nil {
159                self.userEpochData[user]!.insert(key: eligibilityEpoch, UserEpochData(
160                    user: user,
161                    epoch: eligibilityEpoch,
162                    vrfMultiplier: vrfMultiplier,
163                    depositAmount: amount,
164                    claimed: false,
165                    won: false,
166                    payoutAmount: 0.0
167                ))
168                
169                if self.epochs[eligibilityEpoch] == nil {
170                    self.epochs[eligibilityEpoch] = EpochData(
171                        epochNumber: eligibilityEpoch,
172                        startTime: VRFExtension.lastEpochStart + (UFix64(eligibilityEpoch - VRFExtension.currentEpoch) * VRFExtension.epochDuration)
173                    )
174                }
175                
176                self.epochs[eligibilityEpoch]!.incrementParticipants()
177            } else {
178                let existing = self.userEpochData[user]![eligibilityEpoch]!
179                self.userEpochData[user]!.insert(key: eligibilityEpoch, UserEpochData(
180                    user: existing.user,
181                    epoch: existing.epoch,
182                    vrfMultiplier: existing.vrfMultiplier,
183                    depositAmount: existing.depositAmount + amount,
184                    claimed: existing.claimed,
185                    won: existing.won,
186                    payoutAmount: existing.payoutAmount
187                ))
188            }
189        }
190        
191        access(all) fun claimReward(user: Address, epochNumber: UInt64): {String: UFix64} {
192            pre {
193                epochNumber < VRFExtension.currentEpoch: "Epoch not completed"
194                epochNumber % VRFExtension.epochsPerPayout == 0: "Not a payout epoch"
195                self.userEpochData[user] != nil: "User not found"
196                self.userEpochData[user]![epochNumber] != nil: "Not eligible for this epoch"
197                !self.userEpochData[user]![epochNumber]!.claimed: "Already claimed"
198            }
199            
200            let vault = self.vaultCap.borrow() ?? panic("Cannot borrow vault")
201            let userPosition = vault.getUserPosition(user: user) ?? panic("No user position")
202            
203            assert(userPosition.yieldEligible, message: "Not yield eligible")
204            
205            let userData = self.userEpochData[user]![epochNumber]!
206            let epochData = self.epochs[epochNumber]!
207            
208            let baseYield = epochData.participantCount > 0 
209                ? epochData.totalYieldPool / UFix64(epochData.participantCount)
210                : 0.0
211            
212            let multiplierOption = self.vrfMultipliers[userData.vrfMultiplier]!
213            
214            let blockHeight = getCurrentBlock().height
215            let timestamp = getCurrentBlock().timestamp
216            let randomSeed = UInt64(blockHeight) * 1000000 + UInt64(timestamp)
217            let normalizedRandom = randomSeed % UInt64(VRFExtension.BASIS_POINTS)
218            let won = UFix64(normalizedRandom) < multiplierOption.probability
219            
220            var actualPayout = 0.0
221            
222            if won {
223                let potentialPayout = baseYield * multiplierOption.multiplier
224                let availableYield = epochData.totalYieldPool - epochData.totalDistributed
225                
226                actualPayout = potentialPayout < availableYield ? potentialPayout : availableYield
227                
228                if actualPayout > 0.0 {
229                    self.epochs[epochNumber]!.recordDistribution(amount: actualPayout)
230                    VRFExtension.totalYieldPool = VRFExtension.totalYieldPool - actualPayout
231                    VRFExtension.totalDistributed = VRFExtension.totalDistributed + actualPayout
232                }
233            }
234            
235            self.userEpochData[user]!.insert(key: epochNumber, UserEpochData(
236                user: userData.user,
237                epoch: userData.epoch,
238                vrfMultiplier: userData.vrfMultiplier,
239                depositAmount: userData.depositAmount,
240                claimed: true,
241                won: won,
242                payoutAmount: actualPayout
243            ))
244            
245            emit RewardClaimed(user: user, epoch: epochNumber, won: won, baseYield: baseYield, actualPayout: actualPayout, multiplier: userData.vrfMultiplier)
246            
247            return {
248                "won": won ? 1.0 : 0.0,
249                "baseYield": baseYield,
250                "actualPayout": actualPayout,
251                "multiplier": userData.vrfMultiplier,
252                "winProbability": multiplierOption.probability
253            }
254        }
255        
256        access(all) fun getEpochInfo(epochNumber: UInt64): EpochData? {
257            return self.epochs[epochNumber]
258        }
259        
260        access(all) fun getUserEpochData(user: Address, epochNumber: UInt64): UserEpochData? {
261            if self.userEpochData[user] == nil {
262                return nil
263            }
264            return self.userEpochData[user]![epochNumber]
265        }
266        
267        access(all) fun getClaimableEpochs(user: Address): [UInt64] {
268            let claimable: [UInt64] = []
269            
270            if self.userEpochData[user] == nil {
271                return claimable
272            }
273            
274            var epoch = VRFExtension.epochsPerPayout
275            while epoch < VRFExtension.currentEpoch {
276                if self.userEpochData[user]![epoch] != nil && !self.userEpochData[user]![epoch]!.claimed {
277                    claimable.append(epoch)
278                }
279                epoch = epoch + VRFExtension.epochsPerPayout
280            }
281            
282            return claimable
283        }
284        
285        access(all) fun getAvailableMultipliers(): [VRFMultiplierOption] {
286            return [
287                self.vrfMultipliers[1.0]!,
288                self.vrfMultipliers[2.0]!,
289                self.vrfMultipliers[5.0]!,
290                self.vrfMultipliers[10.0]!,
291                self.vrfMultipliers[50.0]!,
292                self.vrfMultipliers[100.0]!
293            ]
294        }
295        
296        access(all) fun getCurrentEpochStatus(): {String: AnyStruct} {
297            let endTime = VRFExtension.lastEpochStart + VRFExtension.epochDuration
298            let timeRemaining = getCurrentBlock().timestamp < endTime ? endTime - getCurrentBlock().timestamp : 0.0
299            
300            return {
301                "currentEpoch": VRFExtension.currentEpoch,
302                "timeRemaining": timeRemaining,
303                "yieldPool": VRFExtension.totalYieldPool,
304                "totalDistributed": VRFExtension.totalDistributed,
305                "epochDuration": VRFExtension.epochDuration,
306                "epochsPerPayout": VRFExtension.epochsPerPayout
307            }
308        }
309    }
310    
311    access(all) fun createExtension(vaultCap: Capability<&VaultCore.Vault>): @Extension {
312        return <- create Extension(vaultCap: vaultCap)
313    }
314    
315    access(all) fun getMetrics(): {String: AnyStruct} {
316        return {
317            "currentEpoch": self.currentEpoch,
318            "epochDuration": self.epochDuration,
319            "epochsPerPayout": self.epochsPerPayout,
320            "totalYieldPool": self.totalYieldPool,
321            "totalDistributed": self.totalDistributed
322        }
323    }
324    
325    init() {
326        self.ExtensionStoragePath = /storage/VRFExtension
327        self.ExtensionPublicPath = /public/VRFExtension
328        self.CADENCE_ARCH_VRF = 0xe467b9dd11fa00df
329        self.BASIS_POINTS = 10000.0
330        self.currentEpoch = 1
331        self.epochDuration = 604800.0
332        self.lastEpochStart = getCurrentBlock().timestamp
333        self.epochsPerPayout = 4
334        self.totalYieldPool = 0.0
335        self.totalDistributed = 0.0
336    }
337}