Smart Contract

PrizeSavings

A.262cf58c0b9fbcff.PrizeSavings

Valid From

133,896,309

Deployed

6d ago
Feb 21, 2026, 09:16:15 PM UTC

Dependents

3 imports
1/*
2PrizeSavings - Prize-Linked Savings Protocol
3
4No-loss lottery where users deposit tokens to earn guaranteed savings interest and lottery prizes.
5All rewards are automatically compounded into deposits.
6
7Architecture:
8- ERC4626-style shares model for O(1) interest distribution
9- Modular yield sources via DeFi Actions interface
10- Configurable distribution strategies (savings/lottery/treasury split)
11- Pluggable winner selection strategies (weighted single, multi-winner, fixed tiers)
12- Emergency mode with auto-recovery and health monitoring
13- NFT prize support with pending claims system
14- Direct funding for external sponsors with rate limits
15
16Core Components:
17- SavingsDistributor: Shares-based vault for proportional interest distribution
18- LotteryDistributor: Prize pool and NFT prize management
19- TreasuryDistributor: Protocol reserves and rounding dust collection
20- Pool: Manages deposits, withdrawals, and prize draws per asset type
21*/
22
23import FungibleToken from 0xf233dcee88fe0abe
24import NonFungibleToken from 0x1d7e57aa55817448
25import RandomConsumer from 0x45caec600164c9e6
26import DeFiActions from 0x92195d814edf9cb0
27import DeFiActionsUtils from 0x92195d814edf9cb0
28import PrizeWinnerTracker from 0x262cf58c0b9fbcff
29import Xorshift128plus from 0x262cf58c0b9fbcff
30
31access(all) contract PrizeSavings {
32    
33    access(all) let PoolPositionCollectionStoragePath: StoragePath
34    access(all) let PoolPositionCollectionPublicPath: PublicPath
35    
36    access(all) event PoolCreated(poolID: UInt64, assetType: String, strategy: String)
37    access(all) event Deposited(poolID: UInt64, receiverID: UInt64, amount: UFix64)
38    access(all) event Withdrawn(poolID: UInt64, receiverID: UInt64, amount: UFix64)
39    
40    access(all) event RewardsProcessed(poolID: UInt64, totalAmount: UFix64, savingsAmount: UFix64, lotteryAmount: UFix64)
41    
42    access(all) event SavingsInterestDistributed(poolID: UInt64, amount: UFix64, interestPerShare: UFix64)
43    access(all) event SavingsInterestCompounded(poolID: UInt64, receiverID: UInt64, amount: UFix64)
44    access(all) event SavingsInterestCompoundedBatch(poolID: UInt64, userCount: Int, totalAmount: UFix64, avgAmount: UFix64)
45    access(all) event SavingsRoundingDustToTreasury(poolID: UInt64, amount: UFix64)
46    
47    access(all) event PrizeDrawCommitted(poolID: UInt64, prizeAmount: UFix64, commitBlock: UInt64)
48    access(all) event PrizesAwarded(poolID: UInt64, winners: [UInt64], amounts: [UFix64], round: UInt64)
49    access(all) event LotteryPrizePoolFunded(poolID: UInt64, amount: UFix64, source: String)
50    
51    access(all) event DistributionStrategyUpdated(poolID: UInt64, oldStrategy: String, newStrategy: String, updatedBy: Address)
52    access(all) event WinnerSelectionStrategyUpdated(poolID: UInt64, oldStrategy: String, newStrategy: String, updatedBy: Address)
53    access(all) event WinnerTrackerUpdated(poolID: UInt64, hasOldTracker: Bool, hasNewTracker: Bool, updatedBy: Address)
54    access(all) event DrawIntervalUpdated(poolID: UInt64, oldInterval: UFix64, newInterval: UFix64, updatedBy: Address)
55    access(all) event MinimumDepositUpdated(poolID: UInt64, oldMinimum: UFix64, newMinimum: UFix64, updatedBy: Address)
56    access(all) event PoolCreatedByAdmin(poolID: UInt64, assetType: String, strategy: String, createdBy: Address)
57    
58    access(all) event PoolPaused(poolID: UInt64, pausedBy: Address, reason: String)
59    access(all) event PoolUnpaused(poolID: UInt64, unpausedBy: Address)
60    access(all) event TreasuryFunded(poolID: UInt64, amount: UFix64, source: String)
61    access(all) event TreasuryWithdrawn(poolID: UInt64, withdrawnBy: Address, amount: UFix64, purpose: String, remainingBalance: UFix64)
62    
63    access(all) event BonusLotteryWeightSet(poolID: UInt64, receiverID: UInt64, bonusWeight: UFix64, reason: String, setBy: Address, timestamp: UFix64)
64    access(all) event BonusLotteryWeightAdded(poolID: UInt64, receiverID: UInt64, additionalWeight: UFix64, newTotalBonus: UFix64, reason: String, addedBy: Address, timestamp: UFix64)
65    access(all) event BonusLotteryWeightRemoved(poolID: UInt64, receiverID: UInt64, previousBonus: UFix64, removedBy: Address, timestamp: UFix64)
66    
67    access(all) event NFTPrizeDeposited(poolID: UInt64, nftID: UInt64, nftType: String, depositedBy: Address)
68    access(all) event NFTPrizeAwarded(poolID: UInt64, receiverID: UInt64, nftID: UInt64, nftType: String, round: UInt64)
69    access(all) event NFTPrizeStored(poolID: UInt64, receiverID: UInt64, nftID: UInt64, nftType: String, reason: String)
70    access(all) event NFTPrizeClaimed(poolID: UInt64, receiverID: UInt64, nftID: UInt64, nftType: String)
71    access(all) event NFTPrizeWithdrawn(poolID: UInt64, nftID: UInt64, nftType: String, withdrawnBy: Address)
72    
73    access(all) event PoolEmergencyEnabled(poolID: UInt64, reason: String, enabledBy: Address, timestamp: UFix64)
74    access(all) event PoolEmergencyDisabled(poolID: UInt64, disabledBy: Address, timestamp: UFix64)
75    access(all) event PoolPartialModeEnabled(poolID: UInt64, reason: String, setBy: Address, timestamp: UFix64)
76    access(all) event EmergencyModeAutoTriggered(poolID: UInt64, reason: String, healthScore: UFix64, timestamp: UFix64)
77    access(all) event EmergencyModeAutoRecovered(poolID: UInt64, reason: String, healthScore: UFix64?, duration: UFix64?, timestamp: UFix64)
78    access(all) event EmergencyConfigUpdated(poolID: UInt64, updatedBy: Address)
79    access(all) event WithdrawalFailure(poolID: UInt64, receiverID: UInt64, amount: UFix64, consecutiveFailures: Int, yieldAvailable: UFix64)
80    
81    access(all) event DirectFundingReceived(poolID: UInt64, destination: UInt8, destinationName: String, amount: UFix64, sponsor: Address, purpose: String, metadata: {String: String})
82    
83    access(self) var pools: @{UInt64: Pool}
84    access(self) var nextPoolID: UInt64
85    
86    access(all) enum PoolEmergencyState: UInt8 {
87        access(all) case Normal
88        access(all) case Paused
89        access(all) case EmergencyMode
90        access(all) case PartialMode
91    }
92    
93    access(all) enum PoolFundingDestination: UInt8 {
94        access(all) case Savings
95        access(all) case Lottery
96        access(all) case Treasury
97    }
98    
99    access(all) struct BonusWeightRecord {
100        access(all) let bonusWeight: UFix64
101        access(all) let reason: String
102        access(all) let addedAt: UFix64
103        access(all) let addedBy: Address
104        
105        access(contract) init(bonusWeight: UFix64, reason: String, addedBy: Address) {
106            self.bonusWeight = bonusWeight
107            self.reason = reason
108            self.addedAt = getCurrentBlock().timestamp
109            self.addedBy = addedBy
110        }
111    }
112    
113    access(all) struct EmergencyConfig {
114        access(all) let maxEmergencyDuration: UFix64?
115        access(all) let autoRecoveryEnabled: Bool
116        access(all) let minYieldSourceHealth: UFix64
117        access(all) let maxWithdrawFailures: Int
118        access(all) let partialModeDepositLimit: UFix64?
119        access(all) let minBalanceThreshold: UFix64
120        
121        init(
122            maxEmergencyDuration: UFix64?,
123            autoRecoveryEnabled: Bool,
124            minYieldSourceHealth: UFix64,
125            maxWithdrawFailures: Int,
126            partialModeDepositLimit: UFix64?,
127            minBalanceThreshold: UFix64
128        ) {
129            pre {
130                minYieldSourceHealth >= 0.0 && minYieldSourceHealth <= 1.0: "Health must be between 0.0 and 1.0"
131                maxWithdrawFailures > 0: "Must allow at least 1 withdrawal failure"
132                minBalanceThreshold >= 0.8 && minBalanceThreshold <= 1.0: "Balance threshold must be between 0.8 and 1.0"
133            }
134            self.maxEmergencyDuration = maxEmergencyDuration
135            self.autoRecoveryEnabled = autoRecoveryEnabled
136            self.minYieldSourceHealth = minYieldSourceHealth
137            self.maxWithdrawFailures = maxWithdrawFailures
138            self.partialModeDepositLimit = partialModeDepositLimit
139            self.minBalanceThreshold = minBalanceThreshold
140        }
141    }
142    
143    access(all) struct FundingPolicy {
144        access(all) let maxDirectLottery: UFix64?
145        access(all) let maxDirectTreasury: UFix64?
146        access(all) let maxDirectSavings: UFix64?
147        access(all) var totalDirectLottery: UFix64
148        access(all) var totalDirectTreasury: UFix64
149        access(all) var totalDirectSavings: UFix64
150        
151        init(maxDirectLottery: UFix64?, maxDirectTreasury: UFix64?, maxDirectSavings: UFix64?) {
152            self.maxDirectLottery = maxDirectLottery
153            self.maxDirectTreasury = maxDirectTreasury
154            self.maxDirectSavings = maxDirectSavings
155            self.totalDirectLottery = 0.0
156            self.totalDirectTreasury = 0.0
157            self.totalDirectSavings = 0.0
158        }
159        
160        access(contract) fun recordDirectFunding(destination: PoolFundingDestination, amount: UFix64) {
161            switch destination {
162                case PoolFundingDestination.Lottery:
163                    self.totalDirectLottery = self.totalDirectLottery + amount
164                    if self.maxDirectLottery != nil {
165                        assert(self.totalDirectLottery <= self.maxDirectLottery!, message: "Direct lottery funding limit exceeded")
166                    }
167                case PoolFundingDestination.Treasury:
168                    self.totalDirectTreasury = self.totalDirectTreasury + amount
169                    if self.maxDirectTreasury != nil {
170                        assert(self.totalDirectTreasury <= self.maxDirectTreasury!, message: "Direct treasury funding limit exceeded")
171                    }
172                case PoolFundingDestination.Savings:
173                    self.totalDirectSavings = self.totalDirectSavings + amount
174                    if self.maxDirectSavings != nil {
175                        assert(self.totalDirectSavings <= self.maxDirectSavings!, message: "Direct savings funding limit exceeded")
176                    }
177            }
178        }
179    }
180    
181    access(all) fun createDefaultEmergencyConfig(): EmergencyConfig {
182        return EmergencyConfig(
183            maxEmergencyDuration: 86400.0,
184            autoRecoveryEnabled: true,
185            minYieldSourceHealth: 0.5,
186            maxWithdrawFailures: 3,
187            partialModeDepositLimit: 100.0,
188            minBalanceThreshold: 0.95
189        )
190    }
191    
192    access(all) fun createDefaultFundingPolicy(): FundingPolicy {
193        return FundingPolicy(
194            maxDirectLottery: nil,
195            maxDirectTreasury: nil,
196            maxDirectSavings: nil
197        )
198    }
199    
200    access(all) resource Admin {
201        access(contract) init() {}
202        
203        access(all) fun updatePoolDistributionStrategy(
204            poolID: UInt64,
205            newStrategy: {DistributionStrategy},
206            updatedBy: Address
207        ) {
208            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
209                ?? panic("Pool does not exist")
210            
211            let oldStrategyName = poolRef.getDistributionStrategyName()
212            poolRef.setDistributionStrategy(strategy: newStrategy)
213            let newStrategyName = newStrategy.getStrategyName()
214            
215            emit DistributionStrategyUpdated(
216                poolID: poolID,
217                oldStrategy: oldStrategyName,
218                newStrategy: newStrategyName,
219                updatedBy: updatedBy
220            )
221        }
222        
223        access(all) fun updatePoolWinnerSelectionStrategy(
224            poolID: UInt64,
225            newStrategy: {WinnerSelectionStrategy},
226            updatedBy: Address
227        ) {
228            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
229                ?? panic("Pool does not exist")
230            
231            let oldStrategyName = poolRef.getWinnerSelectionStrategyName()
232            poolRef.setWinnerSelectionStrategy(strategy: newStrategy)
233            let newStrategyName = newStrategy.getStrategyName()
234            
235            emit WinnerSelectionStrategyUpdated(
236                poolID: poolID,
237                oldStrategy: oldStrategyName,
238                newStrategy: newStrategyName,
239                updatedBy: updatedBy
240            )
241        }
242        
243        access(all) fun updatePoolWinnerTracker(
244            poolID: UInt64,
245            newTrackerCap: Capability<&{PrizeWinnerTracker.WinnerTrackerPublic}>?,
246            updatedBy: Address
247        ) {
248            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
249                ?? panic("Pool does not exist")
250            
251            let hasOldTracker = poolRef.hasWinnerTracker()
252            poolRef.setWinnerTrackerCap(cap: newTrackerCap)
253            let hasNewTracker = newTrackerCap != nil
254            
255            emit WinnerTrackerUpdated(
256                poolID: poolID,
257                hasOldTracker: hasOldTracker,
258                hasNewTracker: hasNewTracker,
259                updatedBy: updatedBy
260            )
261        }
262        
263        access(all) fun updatePoolDrawInterval(
264            poolID: UInt64,
265            newInterval: UFix64,
266            updatedBy: Address
267        ) {
268            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
269                ?? panic("Pool does not exist")
270            
271            let oldInterval = poolRef.getConfig().drawIntervalSeconds
272            poolRef.setDrawIntervalSeconds(interval: newInterval)
273            
274            emit DrawIntervalUpdated(
275                poolID: poolID,
276                oldInterval: oldInterval,
277                newInterval: newInterval,
278                updatedBy: updatedBy
279            )
280        }
281        
282        access(all) fun updatePoolMinimumDeposit(
283            poolID: UInt64,
284            newMinimum: UFix64,
285            updatedBy: Address
286        ) {
287            pre {
288                newMinimum >= 0.0: "Minimum deposit cannot be negative"
289            }
290            
291            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
292                ?? panic("Pool does not exist")
293            
294            let oldMinimum = poolRef.getConfig().minimumDeposit
295            poolRef.setMinimumDeposit(minimum: newMinimum)
296            
297            emit MinimumDepositUpdated(
298                poolID: poolID,
299                oldMinimum: oldMinimum,
300                newMinimum: newMinimum,
301                updatedBy: updatedBy
302            )
303        }
304        
305        access(all) fun enableEmergencyMode(poolID: UInt64, reason: String, enabledBy: Address) {
306            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID) ?? panic("Pool does not exist")
307            poolRef.setEmergencyMode(reason: reason)
308            emit PoolEmergencyEnabled(poolID: poolID, reason: reason, enabledBy: enabledBy, timestamp: getCurrentBlock().timestamp)
309        }
310        
311        access(all) fun disableEmergencyMode(poolID: UInt64, disabledBy: Address) {
312            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID) ?? panic("Pool does not exist")
313            poolRef.clearEmergencyMode()
314            emit PoolEmergencyDisabled(poolID: poolID, disabledBy: disabledBy, timestamp: getCurrentBlock().timestamp)
315        }
316        
317        access(all) fun setEmergencyPartialMode(poolID: UInt64, reason: String, setBy: Address) {
318            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID) ?? panic("Pool does not exist")
319            poolRef.setPartialMode(reason: reason)
320            emit PoolPartialModeEnabled(poolID: poolID, reason: reason, setBy: setBy, timestamp: getCurrentBlock().timestamp)
321        }
322        
323        access(all) fun updateEmergencyConfig(poolID: UInt64, newConfig: EmergencyConfig, updatedBy: Address) {
324            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID) ?? panic("Pool does not exist")
325            poolRef.setEmergencyConfig(config: newConfig)
326            emit EmergencyConfigUpdated(poolID: poolID, updatedBy: updatedBy)
327        }
328        
329        access(all) fun fundPoolDirect(
330            poolID: UInt64,
331            destination: PoolFundingDestination,
332            from: @{FungibleToken.Vault},
333            sponsor: Address,
334            purpose: String,
335            metadata: {String: String}?
336        ) {
337            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID) ?? panic("Pool does not exist")
338            let amount = from.balance
339            poolRef.fundDirectInternal(destination: destination, from: <- from, sponsor: sponsor, purpose: purpose, metadata: metadata ?? {})
340            
341            emit DirectFundingReceived(
342                poolID: poolID,
343                destination: destination.rawValue,
344                destinationName: self.getDestinationName(destination),
345                amount: amount,
346                sponsor: sponsor,
347                purpose: purpose,
348                metadata: metadata ?? {}
349            )
350        }
351        
352        access(self) fun getDestinationName(_ destination: PoolFundingDestination): String {
353            switch destination {
354                case PoolFundingDestination.Savings: return "Savings"
355                case PoolFundingDestination.Lottery: return "Lottery"
356                case PoolFundingDestination.Treasury: return "Treasury"
357                default: return "Unknown"
358            }
359        }
360        
361        access(all) fun createPool(
362            config: PoolConfig,
363            emergencyConfig: EmergencyConfig?,
364            fundingPolicy: FundingPolicy?,
365            createdBy: Address
366        ): UInt64 {
367            // Use provided configs or create defaults
368            let finalEmergencyConfig = emergencyConfig 
369                ?? PrizeSavings.createDefaultEmergencyConfig()
370            let finalFundingPolicy = fundingPolicy 
371                ?? PrizeSavings.createDefaultFundingPolicy()
372            
373            let poolID = PrizeSavings.createPool(
374                config: config,
375                emergencyConfig: finalEmergencyConfig,
376                fundingPolicy: finalFundingPolicy
377            )
378            
379            emit PoolCreatedByAdmin(
380                poolID: poolID,
381                assetType: config.assetType.identifier,
382                strategy: config.distributionStrategy.getStrategyName(),
383                createdBy: createdBy
384            )
385            
386            return poolID
387        }
388        
389        access(all) fun processPoolRewards(poolID: UInt64) {
390            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
391                ?? panic("Pool does not exist")
392            
393            poolRef.processRewards()
394        }
395        
396        access(all) fun setPoolState(poolID: UInt64, state: PoolEmergencyState, reason: String?, setBy: Address) {
397            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
398                ?? panic("Pool does not exist")
399            
400            poolRef.setState(state: state, reason: reason)
401            
402            switch state {
403                case PoolEmergencyState.Normal:
404                    emit PoolUnpaused(poolID: poolID, unpausedBy: setBy)
405                case PoolEmergencyState.Paused:
406                    emit PoolPaused(poolID: poolID, pausedBy: setBy, reason: reason ?? "Manual pause")
407                case PoolEmergencyState.EmergencyMode:
408                    emit PoolEmergencyEnabled(poolID: poolID, reason: reason ?? "Emergency", enabledBy: setBy, timestamp: getCurrentBlock().timestamp)
409                case PoolEmergencyState.PartialMode:
410                    emit PoolPartialModeEnabled(poolID: poolID, reason: reason ?? "Partial mode", setBy: setBy, timestamp: getCurrentBlock().timestamp)
411            }
412        }
413        
414        access(all) fun withdrawPoolTreasury(
415            poolID: UInt64,
416            amount: UFix64,
417            purpose: String,
418            withdrawnBy: Address
419        ): @{FungibleToken.Vault} {
420            pre {
421                purpose.length > 0: "Purpose must be specified for treasury withdrawal"
422            }
423            
424            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
425                ?? panic("Pool does not exist")
426            
427            let treasuryVault <- poolRef.withdrawTreasury(
428                amount: amount,
429                withdrawnBy: withdrawnBy,
430                purpose: purpose
431            )
432            
433            emit TreasuryWithdrawn(
434                poolID: poolID,
435                withdrawnBy: withdrawnBy,
436                amount: amount,
437                purpose: purpose,
438                remainingBalance: poolRef.getTreasuryBalance()
439            )
440            
441            return <- treasuryVault
442        }
443        
444        access(all) fun setBonusLotteryWeight(
445            poolID: UInt64,
446            receiverID: UInt64,
447            bonusWeight: UFix64,
448            reason: String,
449            setBy: Address
450        ) {
451            pre {
452                bonusWeight >= 0.0: "Bonus weight cannot be negative"
453            }
454            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
455                ?? panic("Pool does not exist")
456            
457            poolRef.setBonusWeight(receiverID: receiverID, bonusWeight: bonusWeight, reason: reason, setBy: setBy)
458        }
459        
460        access(all) fun addBonusLotteryWeight(
461            poolID: UInt64,
462            receiverID: UInt64,
463            additionalWeight: UFix64,
464            reason: String,
465            addedBy: Address
466        ) {
467            pre {
468                additionalWeight > 0.0: "Additional weight must be positive"
469            }
470            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
471                ?? panic("Pool does not exist")
472            
473            poolRef.addBonusWeight(receiverID: receiverID, additionalWeight: additionalWeight, reason: reason, addedBy: addedBy)
474        }
475        
476        access(all) fun removeBonusLotteryWeight(
477            poolID: UInt64,
478            receiverID: UInt64,
479            removedBy: Address
480        ) {
481            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
482                ?? panic("Pool does not exist")
483            
484            poolRef.removeBonusWeight(receiverID: receiverID, removedBy: removedBy)
485        }
486        
487        access(all) fun depositNFTPrize(
488            poolID: UInt64,
489            nft: @{NonFungibleToken.NFT},
490            depositedBy: Address
491        ) {
492            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
493                ?? panic("Pool does not exist")
494            
495            let nftID = nft.uuid
496            let nftType = nft.getType().identifier
497            
498            poolRef.depositNFTPrize(nft: <- nft)
499            
500            emit NFTPrizeDeposited(
501                poolID: poolID,
502                nftID: nftID,
503                nftType: nftType,
504                depositedBy: depositedBy
505            )
506        }
507        
508        access(all) fun withdrawNFTPrize(
509            poolID: UInt64,
510            nftID: UInt64,
511            withdrawnBy: Address
512        ): @{NonFungibleToken.NFT} {
513            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
514                ?? panic("Pool does not exist")
515            
516            let nft <- poolRef.withdrawNFTPrize(nftID: nftID)
517            let nftType = nft.getType().identifier
518            
519            emit NFTPrizeWithdrawn(
520                poolID: poolID,
521                nftID: nftID,
522                nftType: nftType,
523                withdrawnBy: withdrawnBy
524            )
525            
526            return <- nft
527        }
528    }
529    
530    access(all) let AdminStoragePath: StoragePath
531    access(all) let AdminPublicPath: PublicPath
532    
533    access(all) struct DistributionPlan {
534        access(all) let savingsAmount: UFix64
535        access(all) let lotteryAmount: UFix64
536        access(all) let treasuryAmount: UFix64
537        
538        init(savings: UFix64, lottery: UFix64, treasury: UFix64) {
539            self.savingsAmount = savings
540            self.lotteryAmount = lottery
541            self.treasuryAmount = treasury
542        }
543    }
544    
545    access(all) struct interface DistributionStrategy {
546        access(all) fun calculateDistribution(totalAmount: UFix64): DistributionPlan
547        access(all) fun getStrategyName(): String
548    }
549    
550    access(all) struct FixedPercentageStrategy: DistributionStrategy {
551        access(all) let savingsPercent: UFix64
552        access(all) let lotteryPercent: UFix64
553        access(all) let treasuryPercent: UFix64
554        
555        init(savings: UFix64, lottery: UFix64, treasury: UFix64) {
556            pre {
557                savings + lottery + treasury == 1.0: "Percentages must sum to 1.0"
558                savings >= 0.0 && lottery >= 0.0 && treasury >= 0.0: "Must be non-negative"
559            }
560            self.savingsPercent = savings
561            self.lotteryPercent = lottery
562            self.treasuryPercent = treasury
563        }
564        
565        access(all) fun calculateDistribution(totalAmount: UFix64): DistributionPlan {
566            return DistributionPlan(
567                savings: totalAmount * self.savingsPercent,
568                lottery: totalAmount * self.lotteryPercent,
569                treasury: totalAmount * self.treasuryPercent
570            )
571        }
572        
573        access(all) fun getStrategyName(): String {
574            return "Fixed: "
575                .concat(self.savingsPercent.toString())
576                .concat(" savings, ")
577                .concat(self.lotteryPercent.toString())
578                .concat(" lottery")
579        }
580    }
581    
582    /// ERC4626-style shares-based distributor for O(1) interest distribution
583    access(all) resource SavingsDistributor {
584        access(self) var totalShares: UFix64
585        access(self) var totalAssets: UFix64
586        access(self) let userShares: {UInt64: UFix64}
587        access(all) var totalDistributed: UFix64
588        access(self) let vaultType: Type
589        
590        init(vaultType: Type) {
591            self.totalShares = 0.0
592            self.totalAssets = 0.0
593            self.userShares = {}
594            self.totalDistributed = 0.0
595            self.vaultType = vaultType
596        }
597        
598        access(contract) fun distributeInterestAndReinvest(
599            vault: @{FungibleToken.Vault}, 
600            totalDeposited: UFix64,
601            yieldSink: &{DeFiActions.Sink}
602        ): UFix64 {
603            let amount = vault.balance
604            
605            if amount == 0.0 || self.totalShares == 0.0 {
606                yieldSink.depositCapacity(from: &vault as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
607                destroy vault
608                return 0.0
609            }
610            
611            self.totalAssets = self.totalAssets + amount
612            
613            yieldSink.depositCapacity(from: &vault as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
614            destroy vault
615            
616            self.totalDistributed = self.totalDistributed + amount
617            
618            return amount
619        }
620        
621        /// Mint shares for new deposits (not for compounding interest)
622        access(contract) fun deposit(receiverID: UInt64, amount: UFix64) {
623            if amount == 0.0 {
624                return
625            }
626            
627            let sharesToMint = self.convertToShares(amount)
628            let currentShares = self.userShares[receiverID] ?? 0.0
629            self.userShares[receiverID] = currentShares + sharesToMint
630            self.totalShares = self.totalShares + sharesToMint
631            self.totalAssets = self.totalAssets + amount
632        }
633        
634        access(contract) fun withdraw(receiverID: UInt64, amount: UFix64): UFix64 {
635            if amount == 0.0 {
636                return 0.0
637            }
638            
639            let userShareBalance = self.userShares[receiverID] ?? 0.0
640            assert(userShareBalance > 0.0, message: "No shares to withdraw")
641            assert(self.totalShares > 0.0 && self.totalAssets > 0.0, message: "Invalid distributor state")
642            
643            let currentAssetValue = self.convertToAssets(userShareBalance)
644            assert(amount <= currentAssetValue, message: "Insufficient balance")
645            
646            let sharesToBurn = (amount * self.totalShares) / self.totalAssets
647            
648            self.userShares[receiverID] = userShareBalance - sharesToBurn
649            self.totalShares = self.totalShares - sharesToBurn
650            self.totalAssets = self.totalAssets - amount
651            
652            return amount
653        }
654        
655        access(self) fun convertToShares(_ assets: UFix64): UFix64 {
656            if self.totalShares == 0.0 || self.totalAssets == 0.0 {
657                return assets
658            }
659            
660            if assets > 0.0 && self.totalShares > 0.0 {
661                let maxSafeAssets = UFix64.max / self.totalShares
662                assert(assets <= maxSafeAssets, message: "Deposit amount too large - would cause overflow")
663            }
664            
665            return (assets * self.totalShares) / self.totalAssets
666        }
667        
668        access(self) fun convertToAssets(_ shares: UFix64): UFix64 {
669            if self.totalShares == 0.0 {
670                return 0.0
671            }
672            
673            if shares > 0.0 && self.totalAssets > 0.0 {
674                let maxSafeShares = UFix64.max / self.totalAssets
675                assert(shares <= maxSafeShares, message: "Share amount too large - would cause overflow")
676            }
677            
678            return (shares * self.totalAssets) / self.totalShares
679        }
680        
681        access(all) fun getUserAssetValue(receiverID: UInt64): UFix64 {
682            let userShareBalance = self.userShares[receiverID] ?? 0.0
683            return self.convertToAssets(userShareBalance)
684        }
685        
686        access(all) fun calculatePendingInterest(receiverID: UInt64, deposit: UFix64): UFix64 {
687            let currentValue = self.getUserAssetValue(receiverID: receiverID)
688            return currentValue > deposit ? currentValue - deposit : 0.0
689        }
690        
691        access(contract) fun claimInterest(receiverID: UInt64, deposit: UFix64): UFix64 {
692            return self.calculatePendingInterest(receiverID: receiverID, deposit: deposit)
693        }
694        
695        access(contract) fun withdrawInterestWithYieldSource(
696            amount: UFix64,
697            yieldSource: auth(FungibleToken.Withdraw) &{DeFiActions.Source}
698        ): @{FungibleToken.Vault} {
699            if amount == 0.0 {
700                return <- DeFiActionsUtils.getEmptyVault(self.vaultType)
701            }
702            
703            let availableFromYield = yieldSource.minimumAvailable()
704            let toWithdraw = amount < availableFromYield ? amount : availableFromYield
705            
706            assert(toWithdraw > 0.0, message: "Insufficient interest available in yield source")
707            
708            return <- yieldSource.withdrawAvailable(maxAmount: toWithdraw)
709        }
710        
711        access(contract) fun initializeReceiver(receiverID: UInt64, deposit: UFix64) {
712        }
713        
714        access(contract) fun updateAfterBalanceChange(receiverID: UInt64, newDeposit: UFix64) {
715        }
716        
717        access(all) fun getInterestVaultBalance(): UFix64 {
718            return 0.0
719        }
720        
721        access(all) fun getTotalDistributed(): UFix64 {
722            return self.totalDistributed
723        }
724        
725        access(all) fun getTotalShares(): UFix64 {
726            return self.totalShares
727        }
728        
729        access(all) fun getTotalAssets(): UFix64 {
730            return self.totalAssets
731        }
732        
733        access(all) fun getUserShares(receiverID: UInt64): UFix64 {
734            return self.userShares[receiverID] ?? 0.0
735        }
736    }
737    
738    access(all) resource LotteryDistributor {
739        access(self) var prizeVault: @{FungibleToken.Vault}
740        access(self) var nftPrizeVault: @{UInt64: {NonFungibleToken.NFT}}
741        access(self) var pendingNFTClaims: @{UInt64: [{NonFungibleToken.NFT}]}
742        access(self) var _prizeRound: UInt64
743        access(all) var totalPrizesDistributed: UFix64
744        
745        access(all) fun getPrizeRound(): UInt64 {
746            return self._prizeRound
747        }
748        
749        access(contract) fun setPrizeRound(round: UInt64) {
750            self._prizeRound = round
751        }
752        
753        init(vaultType: Type) {
754            self.prizeVault <- DeFiActionsUtils.getEmptyVault(vaultType)
755            self.nftPrizeVault <- {}
756            self.pendingNFTClaims <- {}
757            self._prizeRound = 0
758            self.totalPrizesDistributed = 0.0
759        }
760        
761        access(contract) fun fundPrizePool(vault: @{FungibleToken.Vault}) {
762            self.prizeVault.deposit(from: <- vault)
763        }
764        
765        access(all) fun getPrizePoolBalance(): UFix64 {
766            return self.prizeVault.balance
767        }
768        
769        access(contract) fun awardPrize(receiverID: UInt64, amount: UFix64, yieldSource: auth(FungibleToken.Withdraw) &{DeFiActions.Source}?): @{FungibleToken.Vault} {
770            self.totalPrizesDistributed = self.totalPrizesDistributed + amount
771            
772            var result <- DeFiActionsUtils.getEmptyVault(self.prizeVault.getType())
773            
774            // If yield source is provided (lottery is reinvested), try to withdraw from it first
775            if yieldSource != nil {
776                let available = yieldSource!.minimumAvailable()
777                if available >= amount {
778                    result.deposit(from: <- yieldSource!.withdrawAvailable(maxAmount: amount))
779                    return <- result
780                } else if available > 0.0 {
781                    // Partial withdrawal from yield source
782                    result.deposit(from: <- yieldSource!.withdrawAvailable(maxAmount: available))
783                }
784            }
785            
786            // Withdraw remainder from internal vault
787            if result.balance < amount {
788                let remaining = amount - result.balance
789                assert(self.prizeVault.balance >= remaining, message: "Insufficient prize pool")
790                result.deposit(from: <- self.prizeVault.withdraw(amount: remaining))
791            }
792            
793            return <- result
794        }
795        
796        access(contract) fun depositNFTPrize(nft: @{NonFungibleToken.NFT}) {
797            let nftID = nft.uuid
798            self.nftPrizeVault[nftID] <-! nft
799        }
800        
801        access(contract) fun withdrawNFTPrize(nftID: UInt64): @{NonFungibleToken.NFT} {
802            let nft <- self.nftPrizeVault.remove(key: nftID)
803            if nft == nil {
804                panic("NFT not found in prize vault")
805            }
806            return <- nft!
807        }
808        
809        access(contract) fun storePendingNFT(receiverID: UInt64, nft: @{NonFungibleToken.NFT}) {
810            if self.pendingNFTClaims[receiverID] == nil {
811                self.pendingNFTClaims[receiverID] <-! []
812            }
813            // Get mutable reference and append
814            let arrayRef = &self.pendingNFTClaims[receiverID] as auth(Mutate) &[{NonFungibleToken.NFT}]?
815            if arrayRef != nil {
816                arrayRef!.append(<- nft)
817            } else {
818                destroy nft
819                panic("Failed to store NFT in pending claims")
820            }
821        }
822        
823        access(all) fun getPendingNFTCount(receiverID: UInt64): Int {
824            return self.pendingNFTClaims[receiverID]?.length ?? 0
825        }
826        
827        access(all) fun getPendingNFTIDs(receiverID: UInt64): [UInt64] {
828            let nfts = &self.pendingNFTClaims[receiverID] as &[{NonFungibleToken.NFT}]?
829            if nfts == nil {
830                return []
831            }
832            
833            let ids: [UInt64] = []
834            for nft in nfts! {
835                ids.append(nft.uuid)
836            }
837            return ids
838        }
839        
840        access(all) fun getAvailableNFTPrizeIDs(): [UInt64] {
841            return self.nftPrizeVault.keys
842        }
843        
844        access(all) fun borrowNFTPrize(nftID: UInt64): &{NonFungibleToken.NFT}? {
845            return &self.nftPrizeVault[nftID]
846        }
847
848        access(contract) fun claimPendingNFT(receiverID: UInt64, nftIndex: Int): @{NonFungibleToken.NFT} {
849            pre {
850                self.pendingNFTClaims[receiverID] != nil: "No pending NFTs for this receiver"
851                nftIndex < self.pendingNFTClaims[receiverID]?.length!: "Invalid NFT index"
852            }
853            return <- self.pendingNFTClaims[receiverID]?.remove(at: nftIndex)!
854        }
855    }
856    
857    access(all) resource TreasuryDistributor {
858        access(self) var treasuryVault: @{FungibleToken.Vault}
859        access(all) var totalCollected: UFix64
860        access(all) var totalWithdrawn: UFix64
861        access(self) var withdrawalHistory: [{String: AnyStruct}]
862        
863        init(vaultType: Type) {
864            self.treasuryVault <- DeFiActionsUtils.getEmptyVault(vaultType)
865            self.totalCollected = 0.0
866            self.totalWithdrawn = 0.0
867            self.withdrawalHistory = []
868        }
869        
870        access(contract) fun deposit(vault: @{FungibleToken.Vault}) {
871            let amount = vault.balance
872            self.totalCollected = self.totalCollected + amount
873            self.treasuryVault.deposit(from: <- vault)
874        }
875        
876        access(all) fun getBalance(): UFix64 {
877            return self.treasuryVault.balance
878        }
879        
880        access(all) fun getTotalCollected(): UFix64 {
881            return self.totalCollected
882        }
883        
884        access(all) fun getTotalWithdrawn(): UFix64 {
885            return self.totalWithdrawn
886        }
887        
888        access(all) fun getWithdrawalHistory(): [{String: AnyStruct}] {
889            return self.withdrawalHistory
890        }
891        
892        access(contract) fun withdraw(amount: UFix64, withdrawnBy: Address, purpose: String): @{FungibleToken.Vault} {
893            pre {
894                self.treasuryVault.balance >= amount: "Insufficient treasury balance"
895                amount > 0.0: "Withdrawal amount must be positive"
896                purpose.length > 0: "Purpose must be specified"
897            }
898            
899            self.totalWithdrawn = self.totalWithdrawn + amount
900            
901            self.withdrawalHistory.append({
902                "address": withdrawnBy,
903                "amount": amount,
904                "timestamp": getCurrentBlock().timestamp,
905                "purpose": purpose
906            })
907            
908            return <- self.treasuryVault.withdraw(amount: amount)
909        }
910    }
911    
912    access(all) resource PrizeDrawReceipt {
913        access(all) let prizeAmount: UFix64
914        access(self) var request: @RandomConsumer.Request?
915        access(all) let timeWeightedStakes: {UInt64: UFix64}  // Snapshot of stakes at draw start
916        
917        init(prizeAmount: UFix64, request: @RandomConsumer.Request, timeWeightedStakes: {UInt64: UFix64}) {
918            self.prizeAmount = prizeAmount
919            self.request <- request
920            self.timeWeightedStakes = timeWeightedStakes
921        }
922        
923        access(all) view fun getRequestBlock(): UInt64? {
924            return self.request?.block
925        }
926        
927        access(contract) fun popRequest(): @RandomConsumer.Request {
928            let request <- self.request <- nil
929            return <- request!
930        }
931        
932        access(all) fun getTimeWeightedStakes(): {UInt64: UFix64} {
933            return self.timeWeightedStakes
934        }
935    }
936    
937    access(all) struct WinnerSelectionResult {
938        access(all) let winners: [UInt64]
939        access(all) let amounts: [UFix64]
940        access(all) let nftIDs: [[UInt64]]
941        
942        init(winners: [UInt64], amounts: [UFix64], nftIDs: [[UInt64]]) {
943            pre {
944                winners.length == amounts.length: "Winners and amounts must have same length"
945                winners.length == nftIDs.length: "Winners and nftIDs must have same length"
946            }
947            self.winners = winners
948            self.amounts = amounts
949            self.nftIDs = nftIDs
950        }
951    }
952    
953    access(all) struct interface WinnerSelectionStrategy {
954        access(all) fun selectWinners(
955            randomNumber: UInt64,
956            receiverDeposits: {UInt64: UFix64},
957            totalPrizeAmount: UFix64
958        ): WinnerSelectionResult
959        access(all) fun getStrategyName(): String
960    }
961    
962    access(all) struct WeightedSingleWinner: WinnerSelectionStrategy {
963        access(all) let nftIDs: [UInt64]
964        
965        init(nftIDs: [UInt64]) {
966            self.nftIDs = nftIDs
967        }
968        
969        access(all) fun selectWinners(
970            randomNumber: UInt64,
971            receiverDeposits: {UInt64: UFix64},
972            totalPrizeAmount: UFix64
973        ): WinnerSelectionResult {
974            let receiverIDs = receiverDeposits.keys
975            
976            if receiverIDs.length == 0 {
977                return WinnerSelectionResult(winners: [], amounts: [], nftIDs: [])
978            }
979            
980            if receiverIDs.length == 1 {
981                return WinnerSelectionResult(
982                    winners: [receiverIDs[0]],
983                    amounts: [totalPrizeAmount],
984                    nftIDs: [self.nftIDs]
985                )
986            }
987            
988            var cumulativeSum: [UFix64] = []
989            var runningTotal: UFix64 = 0.0
990            
991            for receiverID in receiverIDs {
992                runningTotal = runningTotal + receiverDeposits[receiverID]!
993                cumulativeSum.append(runningTotal)
994            }
995            
996            if runningTotal == 0.0 {
997                return WinnerSelectionResult(
998                    winners: [receiverIDs[0]],
999                    amounts: [totalPrizeAmount],
1000                    nftIDs: [self.nftIDs]
1001                )
1002            }
1003            
1004            let scaledRandom = UFix64(randomNumber % 1_000_000_000) / 1_000_000_000.0
1005            let randomValue = scaledRandom * runningTotal
1006            
1007            var winnerIndex = 0
1008            for i, cumSum in cumulativeSum {
1009                if randomValue < cumSum {
1010                    winnerIndex = i
1011                    break
1012                }
1013            }
1014            
1015            return WinnerSelectionResult(
1016                winners: [receiverIDs[winnerIndex]],
1017                amounts: [totalPrizeAmount],
1018                nftIDs: [self.nftIDs]
1019            )
1020        }
1021        
1022        access(all) fun getStrategyName(): String {
1023            return "Weighted Single Winner"
1024        }
1025    }
1026    
1027    access(all) struct MultiWinnerSplit: WinnerSelectionStrategy {
1028        access(all) let winnerCount: Int
1029        access(all) let prizeSplits: [UFix64]
1030        access(all) let nftIDsPerWinner: [[UInt64]]
1031        
1032        init(winnerCount: Int, prizeSplits: [UFix64], nftIDsPerWinner: [UInt64]) {
1033            pre {
1034                winnerCount > 0: "Must have at least one winner"
1035                prizeSplits.length == winnerCount: "Prize splits must match winner count"
1036            }
1037            
1038            var total: UFix64 = 0.0
1039            for split in prizeSplits {
1040                assert(split >= 0.0 && split <= 1.0, message: "Each split must be between 0 and 1")
1041                total = total + split
1042            }
1043            
1044            assert(total == 1.0, message: "Prize splits must sum to 1.0")
1045            
1046            self.winnerCount = winnerCount
1047            self.prizeSplits = prizeSplits
1048            
1049            var nftArray: [[UInt64]] = []
1050            var nftIndex = 0
1051            var winnerIdx = 0
1052            while winnerIdx < winnerCount {
1053                if nftIndex < nftIDsPerWinner.length {
1054                    nftArray.append([nftIDsPerWinner[nftIndex]])
1055                    nftIndex = nftIndex + 1
1056                } else {
1057                    nftArray.append([])
1058                }
1059                winnerIdx = winnerIdx + 1
1060            }
1061            self.nftIDsPerWinner = nftArray
1062        }
1063        
1064        access(all) fun selectWinners(
1065            randomNumber: UInt64,
1066            receiverDeposits: {UInt64: UFix64},
1067            totalPrizeAmount: UFix64
1068        ): WinnerSelectionResult {
1069            let receiverIDs = receiverDeposits.keys
1070            let depositorCount = receiverIDs.length
1071            
1072            if depositorCount == 0 {
1073                return WinnerSelectionResult(winners: [], amounts: [], nftIDs: [])
1074            }
1075            
1076            assert(self.winnerCount <= depositorCount, message: "More winners than depositors")
1077            
1078            if depositorCount == 1 {
1079                let nftIDsForFirst: [UInt64] = self.nftIDsPerWinner.length > 0 ? self.nftIDsPerWinner[0] : []
1080                return WinnerSelectionResult(
1081                    winners: [receiverIDs[0]],
1082                    amounts: [totalPrizeAmount],
1083                    nftIDs: [nftIDsForFirst]
1084                )
1085            }
1086            
1087            var cumulativeSum: [UFix64] = []
1088            var runningTotal: UFix64 = 0.0
1089            var depositsList: [UFix64] = []
1090            
1091            for receiverID in receiverIDs {
1092                let deposit = receiverDeposits[receiverID]!
1093                depositsList.append(deposit)
1094                runningTotal = runningTotal + deposit
1095                cumulativeSum.append(runningTotal)
1096            }
1097            
1098            var selectedWinners: [UInt64] = []
1099            var selectedIndices: {Int: Bool} = {}
1100            var remainingDeposits = depositsList
1101            var remainingIDs = receiverIDs
1102            var remainingCumSum = cumulativeSum
1103            var remainingTotal = runningTotal
1104            
1105            var randomBytes = randomNumber.toBigEndianBytes()
1106            while randomBytes.length < 16 {
1107                randomBytes.appendAll(randomNumber.toBigEndianBytes())
1108            }
1109            var paddedBytes: [UInt8] = []
1110            var padIdx = 0
1111            while padIdx < 16 {
1112                paddedBytes.append(randomBytes[padIdx % randomBytes.length])
1113                padIdx = padIdx + 1
1114            }
1115            
1116            let prg = Xorshift128plus.PRG(
1117                sourceOfRandomness: paddedBytes,
1118                salt: []
1119            )
1120            
1121            var winnerIndex = 0
1122                while winnerIndex < self.winnerCount && remainingIDs.length > 0 && remainingTotal > 0.0 {
1123                    let rng = prg.nextUInt64()
1124                    let scaledRandom = UFix64(rng % 1_000_000_000) / 1_000_000_000.0
1125                    let randomValue = scaledRandom * remainingTotal
1126                
1127                var selectedIdx = 0
1128                for i, cumSum in remainingCumSum {
1129                    if randomValue < cumSum {
1130                        selectedIdx = i
1131                        break
1132                    }
1133                }
1134                
1135                selectedWinners.append(remainingIDs[selectedIdx])
1136                selectedIndices[selectedIdx] = true
1137                var newRemainingIDs: [UInt64] = []
1138                var newRemainingDeposits: [UFix64] = []
1139                var newCumSum: [UFix64] = []
1140                var newRunningTotal: UFix64 = 0.0
1141                
1142                var idx = 0
1143                while idx < remainingIDs.length {
1144                    if idx != selectedIdx {
1145                        newRemainingIDs.append(remainingIDs[idx])
1146                        newRemainingDeposits.append(remainingDeposits[idx])
1147                        newRunningTotal = newRunningTotal + remainingDeposits[idx]
1148                        newCumSum.append(newRunningTotal)
1149                    }
1150                    idx = idx + 1
1151                }
1152                
1153                remainingIDs = newRemainingIDs
1154                remainingDeposits = newRemainingDeposits
1155                remainingCumSum = newCumSum
1156                remainingTotal = newRunningTotal
1157                winnerIndex = winnerIndex + 1
1158            }
1159            
1160            var prizeAmounts: [UFix64] = []
1161            var calculatedSum: UFix64 = 0.0
1162            var idx = 0
1163            
1164            while idx < selectedWinners.length - 1 {
1165                let split = self.prizeSplits[idx]
1166                let amount = totalPrizeAmount * split
1167                prizeAmounts.append(amount)
1168                calculatedSum = calculatedSum + amount
1169                idx = idx + 1
1170            }
1171            
1172            let lastPrize = totalPrizeAmount - calculatedSum
1173            prizeAmounts.append(lastPrize)
1174            
1175            let expectedLast = totalPrizeAmount * self.prizeSplits[selectedWinners.length - 1]
1176            let deviation = lastPrize > expectedLast ? lastPrize - expectedLast : expectedLast - lastPrize
1177            let maxDeviation = totalPrizeAmount * 0.01
1178            assert(deviation <= maxDeviation, message: "Last prize deviation too large - check splits")
1179            
1180            var nftIDsArray: [[UInt64]] = []
1181            var idx2 = 0
1182            while idx2 < selectedWinners.length {
1183                if idx2 < self.nftIDsPerWinner.length {
1184                    nftIDsArray.append(self.nftIDsPerWinner[idx2])
1185                } else {
1186                    nftIDsArray.append([])
1187                }
1188                idx2 = idx2 + 1
1189            }
1190            
1191            return WinnerSelectionResult(
1192                winners: selectedWinners,
1193                amounts: prizeAmounts,
1194                nftIDs: nftIDsArray
1195            )
1196        }
1197        
1198        access(all) fun getStrategyName(): String {
1199            var name = "Multi-Winner (".concat(self.winnerCount.toString()).concat(" winners): ")
1200            var idx = 0
1201            while idx < self.prizeSplits.length {
1202                if idx > 0 {
1203                    name = name.concat(", ")
1204                }
1205                name = name.concat((self.prizeSplits[idx] * 100.0).toString()).concat("%")
1206                idx = idx + 1
1207            }
1208            return name
1209        }
1210    }
1211    
1212    access(all) struct PrizeTier {
1213        access(all) let prizeAmount: UFix64
1214        access(all) let winnerCount: Int
1215        access(all) let name: String
1216        access(all) let nftIDs: [UInt64]
1217        
1218        init(amount: UFix64, count: Int, name: String, nftIDs: [UInt64]) {
1219            pre {
1220                amount > 0.0: "Prize amount must be positive"
1221                count > 0: "Winner count must be positive"
1222                nftIDs.length <= count: "Cannot have more NFTs than winners in tier"
1223            }
1224            self.prizeAmount = amount
1225            self.winnerCount = count
1226            self.name = name
1227            self.nftIDs = nftIDs
1228        }
1229    }
1230    
1231    access(all) struct FixedPrizeTiers: WinnerSelectionStrategy {
1232        access(all) let tiers: [PrizeTier]
1233        
1234        init(tiers: [PrizeTier]) {
1235            pre {
1236                tiers.length > 0: "Must have at least one prize tier"
1237            }
1238            self.tiers = tiers
1239        }
1240        
1241        access(all) fun selectWinners(
1242            randomNumber: UInt64,
1243            receiverDeposits: {UInt64: UFix64},
1244            totalPrizeAmount: UFix64
1245        ): WinnerSelectionResult {
1246            let receiverIDs = receiverDeposits.keys
1247            let depositorCount = receiverIDs.length
1248            
1249            if depositorCount == 0 {
1250                return WinnerSelectionResult(winners: [], amounts: [], nftIDs: [])
1251            }
1252            
1253            var totalNeeded: UFix64 = 0.0
1254            var totalWinnersNeeded = 0
1255            for tier in self.tiers {
1256                totalNeeded = totalNeeded + (tier.prizeAmount * UFix64(tier.winnerCount))
1257                totalWinnersNeeded = totalWinnersNeeded + tier.winnerCount
1258            }
1259            
1260            if totalPrizeAmount < totalNeeded {
1261                return WinnerSelectionResult(winners: [], amounts: [], nftIDs: [])
1262            }
1263            
1264            if totalWinnersNeeded > depositorCount {
1265                return WinnerSelectionResult(winners: [], amounts: [], nftIDs: [])
1266            }
1267            
1268            var cumulativeSum: [UFix64] = []
1269            var runningTotal: UFix64 = 0.0
1270            
1271            for receiverID in receiverIDs {
1272                let deposit = receiverDeposits[receiverID]!
1273                runningTotal = runningTotal + deposit
1274                cumulativeSum.append(runningTotal)
1275            }
1276            
1277            var randomBytes = randomNumber.toBigEndianBytes()
1278            while randomBytes.length < 16 {
1279                randomBytes.appendAll(randomNumber.toBigEndianBytes())
1280            }
1281            var paddedBytes: [UInt8] = []
1282            var padIdx2 = 0
1283            while padIdx2 < 16 {
1284                paddedBytes.append(randomBytes[padIdx2 % randomBytes.length])
1285                padIdx2 = padIdx2 + 1
1286            }
1287            
1288            let prg = Xorshift128plus.PRG(
1289                sourceOfRandomness: paddedBytes,
1290                salt: []
1291            )
1292            
1293            var allWinners: [UInt64] = []
1294            var allPrizes: [UFix64] = []
1295            var allNFTIDs: [[UInt64]] = []
1296            var usedIndices: {Int: Bool} = {}
1297            var remainingIDs = receiverIDs
1298            var remainingCumSum = cumulativeSum
1299            var remainingTotal = runningTotal
1300            
1301            for tier in self.tiers {
1302                var tierWinnerCount = 0
1303                
1304                while tierWinnerCount < tier.winnerCount && remainingIDs.length > 0 && remainingTotal > 0.0 {
1305                    let rng = prg.nextUInt64()
1306                    let scaledRandom = UFix64(rng % 1_000_000_000) / 1_000_000_000.0
1307                    let randomValue = scaledRandom * remainingTotal
1308                    
1309                    var selectedIdx = 0
1310                    for i, cumSum in remainingCumSum {
1311                        if randomValue <= cumSum {
1312                            selectedIdx = i
1313                            break
1314                        }
1315                    }
1316                    
1317                    let winnerID = remainingIDs[selectedIdx]
1318                    allWinners.append(winnerID)
1319                    allPrizes.append(tier.prizeAmount)
1320                    
1321                    if tierWinnerCount < tier.nftIDs.length {
1322                        allNFTIDs.append([tier.nftIDs[tierWinnerCount]])
1323                    } else {
1324                        allNFTIDs.append([])
1325                    }
1326                    
1327                    var newRemainingIDs: [UInt64] = []
1328                    var newRemainingCumSum: [UFix64] = []
1329                    var newRunningTotal: UFix64 = 0.0
1330                    var oldIdx = 0
1331                    
1332                    while oldIdx < remainingIDs.length {
1333                        if oldIdx != selectedIdx {
1334                            newRemainingIDs.append(remainingIDs[oldIdx])
1335                            let deposit = receiverDeposits[remainingIDs[oldIdx]]!
1336                            newRunningTotal = newRunningTotal + deposit
1337                            newRemainingCumSum.append(newRunningTotal)
1338                        }
1339                        oldIdx = oldIdx + 1
1340                    }
1341                    
1342                    remainingIDs = newRemainingIDs
1343                    remainingCumSum = newRemainingCumSum
1344                    remainingTotal = newRunningTotal
1345                    tierWinnerCount = tierWinnerCount + 1
1346                }
1347            }
1348            
1349            return WinnerSelectionResult(
1350                winners: allWinners,
1351                amounts: allPrizes,
1352                nftIDs: allNFTIDs
1353            )
1354        }
1355        
1356        access(all) fun getStrategyName(): String {
1357            var name = "Fixed Prizes ("
1358            var idx = 0
1359            while idx < self.tiers.length {
1360                if idx > 0 {
1361                    name = name.concat(", ")
1362                }
1363                let tier = self.tiers[idx]
1364                name = name.concat(tier.winnerCount.toString())
1365                    .concat("x ")
1366                    .concat(tier.prizeAmount.toString())
1367                idx = idx + 1
1368            }
1369            return name.concat(")")
1370        }
1371    }
1372    
1373    access(all) struct PoolConfig {
1374        access(all) let assetType: Type
1375        access(all) let yieldConnector: {DeFiActions.Sink, DeFiActions.Source}
1376        access(all) var minimumDeposit: UFix64
1377        access(all) var drawIntervalSeconds: UFix64
1378        access(all) var distributionStrategy: {DistributionStrategy}
1379        access(all) var winnerSelectionStrategy: {WinnerSelectionStrategy}
1380        access(all) var winnerTrackerCap: Capability<&{PrizeWinnerTracker.WinnerTrackerPublic}>?
1381        
1382        init(
1383            assetType: Type,
1384            yieldConnector: {DeFiActions.Sink, DeFiActions.Source},
1385            minimumDeposit: UFix64,
1386            drawIntervalSeconds: UFix64,
1387            distributionStrategy: {DistributionStrategy},
1388            winnerSelectionStrategy: {WinnerSelectionStrategy},
1389            winnerTrackerCap: Capability<&{PrizeWinnerTracker.WinnerTrackerPublic}>?
1390        ) {
1391            self.assetType = assetType
1392            self.yieldConnector = yieldConnector
1393            self.minimumDeposit = minimumDeposit
1394            self.drawIntervalSeconds = drawIntervalSeconds
1395            self.distributionStrategy = distributionStrategy
1396            self.winnerSelectionStrategy = winnerSelectionStrategy
1397            self.winnerTrackerCap = winnerTrackerCap
1398        }
1399        
1400        access(contract) fun setDistributionStrategy(strategy: {DistributionStrategy}) {
1401            self.distributionStrategy = strategy
1402        }
1403        
1404        access(contract) fun setWinnerSelectionStrategy(strategy: {WinnerSelectionStrategy}) {
1405            self.winnerSelectionStrategy = strategy
1406        }
1407        
1408        access(contract) fun setWinnerTrackerCap(cap: Capability<&{PrizeWinnerTracker.WinnerTrackerPublic}>?) {
1409            self.winnerTrackerCap = cap
1410        }
1411        
1412        access(contract) fun setDrawIntervalSeconds(interval: UFix64) {
1413            pre {
1414                interval >= 1.0: "Draw interval must be at least 1 seconds"
1415            }
1416            self.drawIntervalSeconds = interval
1417        }
1418        
1419        access(contract) fun setMinimumDeposit(minimum: UFix64) {
1420            pre {
1421                minimum >= 0.0: "Minimum deposit cannot be negative"
1422            }
1423            self.minimumDeposit = minimum
1424        }
1425    }
1426    
1427    access(all) resource Pool {
1428        access(self) var config: PoolConfig
1429        access(self) var poolID: UInt64
1430        
1431        access(self) var emergencyState: PoolEmergencyState
1432        access(self) var emergencyReason: String?
1433        access(self) var emergencyActivatedAt: UFix64?
1434        access(self) var emergencyConfig: EmergencyConfig
1435        access(self) var consecutiveWithdrawFailures: Int
1436        
1437        access(self) var fundingPolicy: FundingPolicy
1438        
1439        access(contract) fun setPoolID(id: UInt64) {
1440            self.poolID = id
1441        }
1442        
1443        access(self) let receiverDeposits: {UInt64: UFix64}
1444        access(self) let receiverTotalEarnedSavings: {UInt64: UFix64}
1445        access(self) let receiverTotalEarnedPrizes: {UInt64: UFix64}
1446        access(self) let receiverPrizes: {UInt64: UFix64}
1447        access(self) let registeredReceivers: {UInt64: Bool}
1448        access(self) let receiverBonusWeights: {UInt64: BonusWeightRecord}
1449        access(all) var totalDeposited: UFix64
1450        access(all) var totalStaked: UFix64
1451        access(all) var lotteryStaked: UFix64
1452        access(all) var lastDrawTimestamp: UFix64
1453        access(self) let savingsDistributor: @SavingsDistributor
1454        access(self) let lotteryDistributor: @LotteryDistributor
1455        access(self) let treasuryDistributor: @TreasuryDistributor
1456        
1457        access(self) var liquidVault: @{FungibleToken.Vault}
1458        access(self) var pendingDrawReceipt: @PrizeDrawReceipt?
1459        access(self) let randomConsumer: @RandomConsumer.Consumer
1460        
1461        init(
1462            config: PoolConfig, 
1463            initialVault: @{FungibleToken.Vault},
1464            emergencyConfig: EmergencyConfig?,
1465            fundingPolicy: FundingPolicy?
1466        ) {
1467            pre {
1468                initialVault.getType() == config.assetType: "Vault type mismatch"
1469                initialVault.balance == 0.0: "Initial vault must be empty"
1470            }
1471            
1472            self.config = config
1473            self.poolID = 0
1474            
1475            self.emergencyState = PoolEmergencyState.Normal
1476            self.emergencyReason = nil
1477            self.emergencyActivatedAt = nil
1478            self.emergencyConfig = emergencyConfig ?? PrizeSavings.createDefaultEmergencyConfig()
1479            self.consecutiveWithdrawFailures = 0
1480            
1481            self.fundingPolicy = fundingPolicy ?? PrizeSavings.createDefaultFundingPolicy()
1482            
1483            self.receiverDeposits = {}
1484            self.receiverTotalEarnedSavings = {}
1485            self.receiverTotalEarnedPrizes = {}
1486            self.receiverPrizes = {}
1487            self.registeredReceivers = {}
1488            self.receiverBonusWeights = {}
1489            self.totalDeposited = 0.0
1490            self.totalStaked = 0.0
1491            self.lotteryStaked = 0.0
1492            self.lastDrawTimestamp = 0.0
1493            
1494            self.savingsDistributor <- create SavingsDistributor(vaultType: config.assetType)
1495            self.lotteryDistributor <- create LotteryDistributor(vaultType: config.assetType)
1496            self.treasuryDistributor <- create TreasuryDistributor(vaultType: config.assetType)
1497            
1498            self.liquidVault <- initialVault
1499            self.pendingDrawReceipt <- nil
1500            self.randomConsumer <- RandomConsumer.createConsumer()
1501        }
1502        
1503        access(all) fun registerReceiver(receiverID: UInt64) {
1504            pre {
1505                self.registeredReceivers[receiverID] == nil: "Receiver already registered"
1506            }
1507            self.registeredReceivers[receiverID] = true
1508        }
1509        
1510        
1511        access(all) view fun getEmergencyState(): PoolEmergencyState {
1512            return self.emergencyState
1513        }
1514        
1515        access(all) view fun getEmergencyConfig(): EmergencyConfig {
1516            return self.emergencyConfig
1517        }
1518        
1519        access(contract) fun setState(state: PoolEmergencyState, reason: String?) {
1520            self.emergencyState = state
1521            if state != PoolEmergencyState.Normal {
1522                self.emergencyReason = reason
1523                self.emergencyActivatedAt = getCurrentBlock().timestamp
1524            } else {
1525                self.emergencyReason = nil
1526                self.emergencyActivatedAt = nil
1527                self.consecutiveWithdrawFailures = 0
1528            }
1529        }
1530        
1531        access(contract) fun setEmergencyMode(reason: String) {
1532            self.emergencyState = PoolEmergencyState.EmergencyMode
1533            self.emergencyReason = reason
1534            self.emergencyActivatedAt = getCurrentBlock().timestamp
1535        }
1536        
1537        access(contract) fun setPartialMode(reason: String) {
1538            self.emergencyState = PoolEmergencyState.PartialMode
1539            self.emergencyReason = reason
1540            self.emergencyActivatedAt = getCurrentBlock().timestamp
1541        }
1542        
1543        access(contract) fun clearEmergencyMode() {
1544            self.emergencyState = PoolEmergencyState.Normal
1545            self.emergencyReason = nil
1546            self.emergencyActivatedAt = nil
1547            self.consecutiveWithdrawFailures = 0
1548        }
1549        
1550        access(contract) fun setEmergencyConfig(config: EmergencyConfig) {
1551            self.emergencyConfig = config
1552        }
1553        
1554        access(all) view fun isEmergencyMode(): Bool {
1555            return self.emergencyState == PoolEmergencyState.EmergencyMode
1556        }
1557        
1558        access(all) view fun isPartialMode(): Bool {
1559            return self.emergencyState == PoolEmergencyState.PartialMode
1560        }
1561        
1562        access(all) fun getEmergencyInfo(): {String: AnyStruct}? {
1563            if self.emergencyState != PoolEmergencyState.Normal {
1564                let duration = getCurrentBlock().timestamp - (self.emergencyActivatedAt ?? 0.0)
1565                let health = self.checkYieldSourceHealth()
1566                return {
1567                    "state": self.emergencyState.rawValue,
1568                    "reason": self.emergencyReason ?? "Unknown",
1569                    "activatedAt": self.emergencyActivatedAt ?? 0.0,
1570                    "durationSeconds": duration,
1571                    "yieldSourceHealth": health,
1572                    "canAutoRecover": self.emergencyConfig.autoRecoveryEnabled,
1573                    "maxDuration": self.emergencyConfig.maxEmergencyDuration
1574                }
1575            }
1576            return nil
1577        }
1578        
1579        access(contract) fun checkYieldSourceHealth(): UFix64 {
1580            let yieldSource = &self.config.yieldConnector as &{DeFiActions.Source}
1581            let balance = yieldSource.minimumAvailable()
1582            let threshold = self.getEmergencyConfig().minBalanceThreshold
1583            let balanceHealthy = balance >= self.totalStaked * threshold
1584            let withdrawSuccessRate = self.consecutiveWithdrawFailures == 0 ? 1.0 : 
1585                (1.0 / UFix64(self.consecutiveWithdrawFailures + 1))
1586            
1587            var health: UFix64 = 0.0
1588            if balanceHealthy { health = health + 0.5 }
1589            health = health + (withdrawSuccessRate * 0.5)
1590            return health
1591        }
1592        
1593        access(contract) fun checkAndAutoTriggerEmergency(): Bool {
1594            if self.emergencyState != PoolEmergencyState.Normal {
1595                return false
1596            }
1597            
1598            let health = self.checkYieldSourceHealth()
1599            if health < self.emergencyConfig.minYieldSourceHealth {
1600                self.setEmergencyMode(reason: "Auto-triggered: Yield source health below threshold (".concat(health.toString()).concat(")"))
1601                emit EmergencyModeAutoTriggered(poolID: self.poolID, reason: "Low yield source health", healthScore: health, timestamp: getCurrentBlock().timestamp)
1602                return true
1603            }
1604            
1605            if self.consecutiveWithdrawFailures >= self.emergencyConfig.maxWithdrawFailures {
1606                self.setEmergencyMode(reason: "Auto-triggered: Multiple consecutive withdrawal failures")
1607                emit EmergencyModeAutoTriggered(poolID: self.poolID, reason: "Withdrawal failures", healthScore: health, timestamp: getCurrentBlock().timestamp)
1608                return true
1609            }
1610            
1611            return false
1612        }
1613        
1614        access(contract) fun checkAndAutoRecover(): Bool {
1615            if self.emergencyState != PoolEmergencyState.EmergencyMode {
1616                return false
1617            }
1618            
1619            if !self.emergencyConfig.autoRecoveryEnabled {
1620                return false
1621            }
1622            
1623            if let maxDuration = self.emergencyConfig.maxEmergencyDuration {
1624                let duration = getCurrentBlock().timestamp - (self.emergencyActivatedAt ?? 0.0)
1625                if duration > maxDuration {
1626                    self.clearEmergencyMode()
1627                    emit EmergencyModeAutoRecovered(poolID: self.poolID, reason: "Max duration exceeded", healthScore: nil, duration: duration, timestamp: getCurrentBlock().timestamp)
1628                    return true
1629                }
1630            }
1631            
1632            let health = self.checkYieldSourceHealth()
1633            if health >= 0.9 {
1634                self.clearEmergencyMode()
1635                emit EmergencyModeAutoRecovered(poolID: self.poolID, reason: "Yield source recovered", healthScore: health, duration: nil, timestamp: getCurrentBlock().timestamp)
1636                return true
1637            }
1638            
1639            return false
1640        }
1641        
1642        
1643        access(contract) fun distributeSavingsInterest(vault: @{FungibleToken.Vault}) {
1644            let amount = vault.balance
1645            let totalDepositedSnapshot = self.totalDeposited
1646            
1647            self.compoundAllPendingSavings()
1648            
1649            let interestPerShare = self.savingsDistributor.distributeInterestAndReinvest(
1650                vault: <- vault,
1651                totalDeposited: totalDepositedSnapshot,
1652                yieldSink: &self.config.yieldConnector as &{DeFiActions.Sink}
1653            )
1654            
1655            let totalCompounded = self.compoundAllPendingSavings()
1656            
1657            if totalCompounded < amount {
1658                let dust = amount - totalCompounded
1659                let dustVault <- self.config.yieldConnector.withdrawAvailable(maxAmount: dust)
1660                self.treasuryDistributor.deposit(vault: <- dustVault)
1661                
1662                emit SavingsRoundingDustToTreasury(poolID: self.poolID, amount: dust)
1663            }
1664            
1665            emit SavingsInterestDistributed(poolID: self.poolID, amount: amount, interestPerShare: interestPerShare)
1666        }
1667        
1668        access(contract) fun compoundAllPendingSavings(): UFix64 {
1669            let receiverIDs = self.getRegisteredReceiverIDs()
1670            var totalCompounded: UFix64 = 0.0
1671            var usersCompounded: Int = 0
1672            
1673            for receiverID in receiverIDs {
1674                let currentDeposit = self.receiverDeposits[receiverID] ?? 0.0
1675                if currentDeposit > 0.0 {
1676                let pending = self.savingsDistributor.claimInterest(receiverID: receiverID, deposit: currentDeposit)
1677                
1678                if pending > 0.0 {
1679                    let newDeposit = currentDeposit + pending
1680                        self.receiverDeposits[receiverID] = newDeposit
1681                        self.totalDeposited = self.totalDeposited + pending
1682                        totalCompounded = totalCompounded + pending
1683                        self.totalStaked = self.totalStaked + pending
1684                        
1685                        let currentSavings = self.receiverTotalEarnedSavings[receiverID] ?? 0.0
1686                        self.receiverTotalEarnedSavings[receiverID] = currentSavings + pending
1687                        
1688                        usersCompounded = usersCompounded + 1
1689                    }
1690                }
1691            }
1692            
1693            if usersCompounded > 0 {
1694                let avgAmount = totalCompounded / UFix64(usersCompounded)
1695                emit SavingsInterestCompoundedBatch(
1696                    poolID: self.poolID,
1697                    userCount: usersCompounded,
1698                    totalAmount: totalCompounded,
1699                    avgAmount: avgAmount
1700                )
1701            }
1702            
1703            return totalCompounded
1704        }
1705        
1706        access(contract) fun fundDirectInternal(
1707            destination: PoolFundingDestination,
1708            from: @{FungibleToken.Vault},
1709            sponsor: Address,
1710            purpose: String,
1711            metadata: {String: String}
1712        ) {
1713            pre {
1714                self.emergencyState == PoolEmergencyState.Normal: "Direct funding only in normal state"
1715                from.getType() == self.config.assetType: "Invalid vault type"
1716            }
1717            
1718            let amount = from.balance
1719            self.fundingPolicy.recordDirectFunding(destination: destination, amount: amount)
1720            
1721            switch destination {
1722                case PoolFundingDestination.Lottery:
1723                    self.lotteryDistributor.fundPrizePool(vault: <- from)
1724                case PoolFundingDestination.Treasury:
1725                    self.treasuryDistributor.deposit(vault: <- from)
1726                case PoolFundingDestination.Savings:
1727                    self.distributeSavingsInterest(vault: <- from)
1728                default:
1729                    panic("Unsupported funding destination")
1730            }
1731        }
1732        
1733        access(all) fun getFundingStats(): {String: UFix64} {
1734            return {
1735                "totalDirectLottery": self.fundingPolicy.totalDirectLottery,
1736                "totalDirectTreasury": self.fundingPolicy.totalDirectTreasury,
1737                "totalDirectSavings": self.fundingPolicy.totalDirectSavings,
1738                "maxDirectLottery": self.fundingPolicy.maxDirectLottery ?? 0.0,
1739                "maxDirectTreasury": self.fundingPolicy.maxDirectTreasury ?? 0.0,
1740                "maxDirectSavings": self.fundingPolicy.maxDirectSavings ?? 0.0
1741            }
1742        }
1743        
1744        access(all) fun deposit(from: @{FungibleToken.Vault}, receiverID: UInt64) {
1745            pre {
1746                from.balance > 0.0: "Deposit amount must be positive"
1747                from.getType() == self.config.assetType: "Invalid vault type"
1748                self.registeredReceivers[receiverID] == true: "Receiver not registered"
1749            }
1750            
1751            switch self.emergencyState {
1752                case PoolEmergencyState.Normal:
1753                    assert(from.balance >= self.config.minimumDeposit, message: "Below minimum deposit of ".concat(self.config.minimumDeposit.toString()))
1754                case PoolEmergencyState.PartialMode:
1755                    let depositLimit = self.emergencyConfig.partialModeDepositLimit ?? 0.0
1756                    assert(depositLimit > 0.0, message: "Partial mode deposit limit not configured")
1757                    assert(from.balance <= depositLimit, message: "Deposit exceeds partial mode limit of ".concat(depositLimit.toString()))
1758                case PoolEmergencyState.EmergencyMode:
1759                    panic("Deposits disabled in emergency mode. Withdrawals only.")
1760                case PoolEmergencyState.Paused:
1761                    panic("Pool is paused. No operations allowed.")
1762            }
1763            
1764            let amount = from.balance
1765            let isFirstDeposit = (self.receiverDeposits[receiverID] ?? 0.0) == 0.0
1766            var pendingCompounded: UFix64 = 0.0
1767            
1768            if !isFirstDeposit {
1769                let currentDeposit = self.receiverDeposits[receiverID]!
1770                let pending = self.savingsDistributor.claimInterest(receiverID: receiverID, deposit: currentDeposit)
1771                if pending > 0.0 {
1772                    self.receiverDeposits[receiverID] = currentDeposit + pending
1773                    pendingCompounded = pending
1774                    self.totalDeposited = self.totalDeposited + pending
1775                    self.totalStaked = self.totalStaked + pending
1776                    
1777                    let currentSavings = self.receiverTotalEarnedSavings[receiverID] ?? 0.0
1778                    self.receiverTotalEarnedSavings[receiverID] = currentSavings + pending
1779                    emit SavingsInterestCompounded(poolID: self.poolID, receiverID: receiverID, amount: pending)
1780                }
1781            }
1782            
1783            self.savingsDistributor.deposit(receiverID: receiverID, amount: amount)
1784            
1785            let newDeposit = (self.receiverDeposits[receiverID] ?? 0.0) + amount
1786            self.receiverDeposits[receiverID] = newDeposit
1787            self.totalDeposited = self.totalDeposited + amount
1788            self.totalStaked = self.totalStaked + amount
1789            
1790            self.config.yieldConnector.depositCapacity(from: &from as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
1791            destroy from
1792            emit Deposited(poolID: self.poolID, receiverID: receiverID, amount: amount)
1793        }
1794        
1795        access(all) fun withdraw(amount: UFix64, receiverID: UInt64): @{FungibleToken.Vault} {
1796            pre {
1797                amount > 0.0: "Withdrawal amount must be positive"
1798                self.registeredReceivers[receiverID] == true: "Receiver not registered"
1799            }
1800            
1801            assert(self.emergencyState != PoolEmergencyState.Paused, message: "Pool is paused - no operations allowed")
1802            
1803            if self.emergencyState == PoolEmergencyState.EmergencyMode {
1804                self.checkAndAutoRecover()
1805            }
1806            
1807            let receiverDeposit = self.receiverDeposits[receiverID] ?? 0.0
1808            assert(receiverDeposit >= amount, message: "Insufficient deposit. You have ".concat(receiverDeposit.toString()).concat(" but trying to withdraw ").concat(amount.toString()))
1809            
1810            if self.emergencyState == PoolEmergencyState.EmergencyMode {
1811                log("⚠️  Emergency withdrawal - skipping interest compounding")
1812            } else {
1813                let pending = self.savingsDistributor.claimInterest(receiverID: receiverID, deposit: receiverDeposit)
1814                if pending > 0.0 {
1815                    let newDepositWithPending = receiverDeposit + pending
1816                    self.receiverDeposits[receiverID] = newDepositWithPending
1817                    self.totalDeposited = self.totalDeposited + pending
1818                    self.totalStaked = self.totalStaked + pending
1819                    let currentSavings = self.receiverTotalEarnedSavings[receiverID] ?? 0.0
1820                    self.receiverTotalEarnedSavings[receiverID] = currentSavings + pending
1821                    emit SavingsInterestCompounded(poolID: self.poolID, receiverID: receiverID, amount: pending)
1822                }
1823            }
1824            
1825            self.savingsDistributor.withdraw(receiverID: receiverID, amount: amount)
1826            
1827            let currentDeposit = self.receiverDeposits[receiverID] ?? 0.0
1828            let newDeposit = currentDeposit - amount
1829            self.receiverDeposits[receiverID] = newDeposit
1830            self.totalDeposited = self.totalDeposited - amount
1831            
1832            var withdrawn <- DeFiActionsUtils.getEmptyVault(self.config.assetType)
1833            var withdrawalFailed = false
1834            var amountFromYieldSource: UFix64 = 0.0
1835            
1836            if self.emergencyState == PoolEmergencyState.EmergencyMode {
1837                let yieldAvailable = self.config.yieldConnector.minimumAvailable()
1838                if yieldAvailable >= amount {
1839                    let yieldVault <- self.config.yieldConnector.withdrawAvailable(maxAmount: amount)
1840                    amountFromYieldSource = yieldVault.balance
1841                    withdrawn.deposit(from: <- yieldVault)
1842                } else {
1843                    log("⚠️  Yield source insufficient in emergency, using liquid vault")
1844                    withdrawalFailed = true
1845                }
1846            } else {
1847                let yieldAvailable = self.config.yieldConnector.minimumAvailable()
1848                if yieldAvailable >= amount {
1849                    let yieldVault <- self.config.yieldConnector.withdrawAvailable(maxAmount: amount)
1850                    amountFromYieldSource = yieldVault.balance
1851                    withdrawn.deposit(from: <- yieldVault)
1852                } else {
1853                    withdrawalFailed = true
1854                }
1855                
1856                if withdrawalFailed {
1857                    self.consecutiveWithdrawFailures = self.consecutiveWithdrawFailures + 1
1858                    emit WithdrawalFailure(poolID: self.poolID, receiverID: receiverID, amount: amount,
1859                        consecutiveFailures: self.consecutiveWithdrawFailures, yieldAvailable: yieldAvailable)
1860                    self.checkAndAutoTriggerEmergency()
1861                } else {
1862                    self.consecutiveWithdrawFailures = 0
1863                }
1864            }
1865            
1866            // Use liquid vault if needed
1867            if withdrawn.balance < amount {
1868                let remaining = amount - withdrawn.balance
1869                withdrawn.deposit(from: <- self.liquidVault.withdraw(amount: remaining))
1870            }
1871            
1872            // Only decrease totalStaked by amount withdrawn from yield source (not liquid vault fallback)
1873            self.totalStaked = self.totalStaked - amountFromYieldSource
1874            
1875            emit Withdrawn(poolID: self.poolID, receiverID: receiverID, amount: amount)
1876            return <- withdrawn
1877        }
1878        
1879        access(contract) fun processRewards() {
1880            let yieldBalance = self.config.yieldConnector.minimumAvailable()
1881            let availableYield = yieldBalance > self.totalStaked ? yieldBalance - self.totalStaked : 0.0
1882            
1883            if availableYield == 0.0 {
1884                return
1885            }
1886            
1887            let yieldRewards <- self.config.yieldConnector.withdrawAvailable(maxAmount: availableYield)
1888            let totalRewards = yieldRewards.balance
1889            let plan = self.config.distributionStrategy.calculateDistribution(totalAmount: totalRewards)
1890            
1891            if plan.savingsAmount > 0.0 {
1892                // Process rewards: collect from yield source, invest and distribute
1893                let savingsVault <- yieldRewards.withdraw(amount: plan.savingsAmount)
1894                self.distributeSavingsInterest(vault: <- savingsVault)
1895            }
1896            
1897            if plan.lotteryAmount > 0.0 {
1898                let lotteryVault <- yieldRewards.withdraw(amount: plan.lotteryAmount)
1899                self.lotteryDistributor.fundPrizePool(vault: <- lotteryVault)
1900                emit LotteryPrizePoolFunded(
1901                    poolID: self.poolID,
1902                    amount: plan.lotteryAmount,
1903                    source: "yield"
1904                )
1905            }
1906            
1907            if plan.treasuryAmount > 0.0 {
1908                let treasuryVault <- yieldRewards.withdraw(amount: plan.treasuryAmount)
1909                self.treasuryDistributor.deposit(vault: <- treasuryVault)
1910                emit TreasuryFunded(
1911                    poolID: self.poolID,
1912                    amount: plan.treasuryAmount,
1913                    source: "yield"
1914                )
1915            }
1916            
1917            destroy yieldRewards
1918            
1919            emit RewardsProcessed(
1920                poolID: self.poolID,
1921                totalAmount: totalRewards,
1922                savingsAmount: plan.savingsAmount,
1923                lotteryAmount: plan.lotteryAmount
1924            )
1925        }
1926        
1927        access(all) fun startDraw() {
1928            pre {
1929                self.emergencyState == PoolEmergencyState.Normal: "Draws disabled - pool state: ".concat(self.emergencyState.rawValue.toString())
1930                self.pendingDrawReceipt == nil: "Draw already in progress"
1931            }
1932            
1933            assert(self.canDrawNow(), message: "Not enough blocks since last draw")
1934            
1935            if self.checkAndAutoTriggerEmergency() {
1936                panic("Emergency mode auto-triggered - cannot start draw")
1937            }
1938            
1939            let timeWeightedStakes: {UInt64: UFix64} = {}
1940            for receiverID in self.receiverDeposits.keys {
1941                let deposit = self.receiverDeposits[receiverID]!
1942                let bonusWeight = self.getBonusWeight(receiverID: receiverID)
1943                let stake = deposit + bonusWeight
1944                
1945                if stake > 0.0 {
1946                    timeWeightedStakes[receiverID] = stake
1947                }
1948            }
1949            
1950            let prizeAmount = self.lotteryDistributor.getPrizePoolBalance()
1951            assert(prizeAmount > 0.0, message: "No prize pool funds")
1952            
1953            let randomRequest <- self.randomConsumer.requestRandomness()
1954            let receipt <- create PrizeDrawReceipt(
1955                prizeAmount: prizeAmount,
1956                request: <- randomRequest,
1957                timeWeightedStakes: timeWeightedStakes
1958            )
1959            emit PrizeDrawCommitted(
1960                poolID: self.poolID,
1961                prizeAmount: prizeAmount,
1962                commitBlock: receipt.getRequestBlock()!
1963            )
1964            
1965            self.pendingDrawReceipt <-! receipt
1966            self.lastDrawTimestamp = getCurrentBlock().timestamp
1967        }
1968        
1969        access(all) fun completeDraw() {
1970            pre {
1971                self.pendingDrawReceipt != nil: "No draw in progress"
1972            }
1973            
1974            let receipt <- self.pendingDrawReceipt <- nil
1975            let unwrappedReceipt <- receipt!
1976            let totalPrizeAmount = unwrappedReceipt.prizeAmount
1977            
1978            let timeWeightedStakes = unwrappedReceipt.getTimeWeightedStakes()
1979            
1980            let request <- unwrappedReceipt.popRequest()
1981            let randomNumber = self.randomConsumer.fulfillRandomRequest(<- request)
1982            destroy unwrappedReceipt
1983            
1984            let selectionResult = self.config.winnerSelectionStrategy.selectWinners(
1985                randomNumber: randomNumber,
1986                receiverDeposits: timeWeightedStakes,
1987                totalPrizeAmount: totalPrizeAmount
1988            )
1989            
1990            let winners = selectionResult.winners
1991            let prizeAmounts = selectionResult.amounts
1992            let nftIDsPerWinner = selectionResult.nftIDs
1993            
1994            if winners.length == 0 {
1995                emit PrizesAwarded(
1996                    poolID: self.poolID,
1997                    winners: [],
1998                    amounts: [],
1999                    round: self.lotteryDistributor.getPrizeRound()
2000                )
2001                log("⚠️  Draw completed with no depositors - ".concat(totalPrizeAmount.toString()).concat(" FLOW stays for next draw"))
2002                return
2003            }
2004            
2005            assert(winners.length == prizeAmounts.length, message: "Winners and prize amounts must match")
2006            assert(winners.length == nftIDsPerWinner.length, message: "Winners and NFT IDs must match")
2007            
2008            let currentRound = self.lotteryDistributor.getPrizeRound() + 1
2009            self.lotteryDistributor.setPrizeRound(round: currentRound)
2010            var totalAwarded: UFix64 = 0.0
2011            var i = 0
2012            while i < winners.length {
2013                let winnerID = winners[i]
2014                let prizeAmount = prizeAmounts[i]
2015                let nftIDsForWinner = nftIDsPerWinner[i]
2016                
2017                let prizeVault <- self.lotteryDistributor.awardPrize(
2018                    receiverID: winnerID,
2019                    amount: prizeAmount,
2020                    yieldSource: nil
2021                )
2022                
2023                let currentDeposit = self.receiverDeposits[winnerID] ?? 0.0
2024                let pendingSavings = self.savingsDistributor.claimInterest(receiverID: winnerID, deposit: currentDeposit)
2025                var newDeposit = currentDeposit
2026                
2027                // DON'T mint shares for pending savings (already in share value)
2028                if pendingSavings > 0.0 {
2029                    newDeposit = newDeposit + pendingSavings
2030                    self.totalDeposited = self.totalDeposited + pendingSavings
2031                    self.totalStaked = self.totalStaked + pendingSavings
2032                    
2033                    let currentSavings = self.receiverTotalEarnedSavings[winnerID] ?? 0.0
2034                    self.receiverTotalEarnedSavings[winnerID] = currentSavings + pendingSavings
2035                }
2036                
2037                // Mint shares for prize amount (ACTUAL new money)
2038                self.savingsDistributor.deposit(receiverID: winnerID, amount: prizeAmount)
2039                
2040                newDeposit = newDeposit + prizeAmount
2041                self.receiverDeposits[winnerID] = newDeposit
2042                self.totalDeposited = self.totalDeposited + prizeAmount
2043                self.totalStaked = self.totalStaked + prizeAmount
2044                self.config.yieldConnector.depositCapacity(from: &prizeVault as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
2045                destroy prizeVault
2046                
2047                let totalPrizes = self.receiverTotalEarnedPrizes[winnerID] ?? 0.0
2048                self.receiverTotalEarnedPrizes[winnerID] = totalPrizes + prizeAmount
2049                
2050                for nftID in nftIDsForWinner {
2051                    let availableNFTs = self.lotteryDistributor.getAvailableNFTPrizeIDs()
2052                    var nftFound = false
2053                    for availableID in availableNFTs {
2054                        if availableID == nftID {
2055                            nftFound = true
2056                            break
2057                        }
2058                    }
2059                    
2060                    if !nftFound {
2061                        continue
2062                    }
2063                    
2064                    let nft <- self.lotteryDistributor.withdrawNFTPrize(nftID: nftID)
2065                    let nftType = nft.getType().identifier
2066                    
2067                    // Store as pending claim - winner claims via separate transaction
2068                    self.lotteryDistributor.storePendingNFT(receiverID: winnerID, nft: <- nft)
2069                    
2070                    emit NFTPrizeStored(
2071                        poolID: self.poolID,
2072                        receiverID: winnerID,
2073                        nftID: nftID,
2074                        nftType: nftType,
2075                        reason: "Lottery win - round ".concat(currentRound.toString())
2076                    )
2077                    
2078                    emit NFTPrizeAwarded(
2079                        poolID: self.poolID,
2080                        receiverID: winnerID,
2081                        nftID: nftID,
2082                        nftType: nftType,
2083                        round: currentRound
2084                    )
2085                }
2086                
2087                totalAwarded = totalAwarded + prizeAmount
2088                i = i + 1
2089            }
2090            
2091            if let trackerCap = self.config.winnerTrackerCap {
2092                if let trackerRef = trackerCap.borrow() {
2093                    var idx = 0
2094                    while idx < winners.length {
2095                        trackerRef.recordWinner(
2096                            poolID: self.poolID,
2097                            round: currentRound,
2098                            winnerReceiverID: winners[idx],
2099                            amount: prizeAmounts[idx],
2100                            nftIDs: nftIDsPerWinner[idx]
2101                        )
2102                        idx = idx + 1
2103                    }
2104                }
2105            }
2106            
2107            emit PrizesAwarded(
2108                poolID: self.poolID,
2109                winners: winners,
2110                amounts: prizeAmounts,
2111                round: currentRound
2112            )
2113        }
2114        
2115        access(all) fun compoundSavingsInterest(receiverID: UInt64): UFix64 {
2116            pre {
2117                self.registeredReceivers[receiverID] == true: "Receiver not registered"
2118            }
2119            
2120            let currentDeposit = self.receiverDeposits[receiverID] ?? 0.0
2121            let pending = self.savingsDistributor.claimInterest(receiverID: receiverID, deposit: currentDeposit)
2122            
2123            if pending > 0.0 {
2124                // DON'T mint new shares for compounded interest!
2125                // The shares already represent this value (from interest distribution)
2126                
2127                // Add pending savings to deposit
2128                let newDeposit = currentDeposit + pending
2129                self.receiverDeposits[receiverID] = newDeposit
2130                self.totalDeposited = self.totalDeposited + pending
2131                self.totalStaked = self.totalStaked + pending
2132                
2133                // Track historical savings for user reference
2134                let currentSavings = self.receiverTotalEarnedSavings[receiverID] ?? 0.0
2135                self.receiverTotalEarnedSavings[receiverID] = currentSavings + pending
2136                
2137                emit SavingsInterestCompounded(poolID: self.poolID, receiverID: receiverID, amount: pending)
2138            }
2139            
2140            return pending
2141        }
2142        
2143        // Admin functions for strategy updates
2144        access(contract) fun getDistributionStrategyName(): String {
2145            return self.config.distributionStrategy.getStrategyName()
2146        }
2147        
2148        access(contract) fun setDistributionStrategy(strategy: {DistributionStrategy}) {
2149            self.config.setDistributionStrategy(strategy: strategy)
2150        }
2151        
2152        access(contract) fun getWinnerSelectionStrategyName(): String {
2153            return self.config.winnerSelectionStrategy.getStrategyName()
2154        }
2155        
2156        access(contract) fun setWinnerSelectionStrategy(strategy: {WinnerSelectionStrategy}) {
2157            self.config.setWinnerSelectionStrategy(strategy: strategy)
2158        }
2159        
2160        access(all) fun hasWinnerTracker(): Bool {
2161            return self.config.winnerTrackerCap != nil
2162        }
2163        
2164        access(contract) fun setWinnerTrackerCap(cap: Capability<&{PrizeWinnerTracker.WinnerTrackerPublic}>?) {
2165            self.config.setWinnerTrackerCap(cap: cap)
2166        }
2167        
2168        access(contract) fun setDrawIntervalSeconds(interval: UFix64) {
2169            assert(!self.isDrawInProgress(), message: "Cannot change draw interval during an active draw")
2170            self.config.setDrawIntervalSeconds(interval: interval)
2171        }
2172        
2173        access(contract) fun setMinimumDeposit(minimum: UFix64) {
2174            self.config.setMinimumDeposit(minimum: minimum)
2175        }
2176        
2177        access(contract) fun setBonusWeight(receiverID: UInt64, bonusWeight: UFix64, reason: String, setBy: Address) {
2178            let timestamp = getCurrentBlock().timestamp
2179            let record = BonusWeightRecord(bonusWeight: bonusWeight, reason: reason, addedBy: setBy)
2180            self.receiverBonusWeights[receiverID] = record
2181            
2182            emit BonusLotteryWeightSet(
2183                poolID: self.poolID,
2184                receiverID: receiverID,
2185                bonusWeight: bonusWeight,
2186                reason: reason,
2187                setBy: setBy,
2188                timestamp: timestamp
2189            )
2190        }
2191        
2192        access(contract) fun addBonusWeight(receiverID: UInt64, additionalWeight: UFix64, reason: String, addedBy: Address) {
2193            let timestamp = getCurrentBlock().timestamp
2194            let currentBonus = self.receiverBonusWeights[receiverID]?.bonusWeight ?? 0.0
2195            let newTotalBonus = currentBonus + additionalWeight
2196            
2197            let record = BonusWeightRecord(bonusWeight: newTotalBonus, reason: reason, addedBy: addedBy)
2198            self.receiverBonusWeights[receiverID] = record
2199            
2200            emit BonusLotteryWeightAdded(
2201                poolID: self.poolID,
2202                receiverID: receiverID,
2203                additionalWeight: additionalWeight,
2204                newTotalBonus: newTotalBonus,
2205                reason: reason,
2206                addedBy: addedBy,
2207                timestamp: timestamp
2208            )
2209        }
2210        
2211        access(contract) fun removeBonusWeight(receiverID: UInt64, removedBy: Address) {
2212            let timestamp = getCurrentBlock().timestamp
2213            let previousBonus = self.receiverBonusWeights[receiverID]?.bonusWeight ?? 0.0
2214            
2215            let _ = self.receiverBonusWeights.remove(key: receiverID)
2216            
2217            emit BonusLotteryWeightRemoved(
2218                poolID: self.poolID,
2219                receiverID: receiverID,
2220                previousBonus: previousBonus,
2221                removedBy: removedBy,
2222                timestamp: timestamp
2223            )
2224        }
2225        
2226        access(all) fun getBonusWeight(receiverID: UInt64): UFix64 {
2227            return self.receiverBonusWeights[receiverID]?.bonusWeight ?? 0.0
2228        }
2229        
2230        access(all) fun getBonusWeightRecord(receiverID: UInt64): BonusWeightRecord? {
2231            return self.receiverBonusWeights[receiverID]
2232        }
2233        
2234        access(all) fun getAllBonusWeightReceivers(): [UInt64] {
2235            return self.receiverBonusWeights.keys
2236        }
2237        
2238        // NFT Prize Management (delegated to LotteryDistributor)
2239        access(contract) fun depositNFTPrize(nft: @{NonFungibleToken.NFT}) {
2240            self.lotteryDistributor.depositNFTPrize(nft: <- nft)
2241        }
2242        
2243        access(contract) fun withdrawNFTPrize(nftID: UInt64): @{NonFungibleToken.NFT} {
2244            return <- self.lotteryDistributor.withdrawNFTPrize(nftID: nftID)
2245        }
2246        
2247        access(all) fun getAvailableNFTPrizeIDs(): [UInt64] {
2248            return self.lotteryDistributor.getAvailableNFTPrizeIDs()
2249        }
2250        
2251        access(all) fun borrowAvailableNFTPrize(nftID: UInt64): &{NonFungibleToken.NFT}? {
2252            return self.lotteryDistributor.borrowNFTPrize(nftID: nftID)
2253        }
2254        
2255        access(all) fun getPendingNFTCount(receiverID: UInt64): Int {
2256            return self.lotteryDistributor.getPendingNFTCount(receiverID: receiverID)
2257        }
2258        
2259        access(all) fun getPendingNFTIDs(receiverID: UInt64): [UInt64] {
2260            return self.lotteryDistributor.getPendingNFTIDs(receiverID: receiverID)
2261        }
2262        
2263        // Note: Pending NFTs cannot be borrowed directly due to Cadence limitations
2264        // Use getPendingNFTIDs() to see what's pending, then claim and view in wallet
2265        
2266        access(all) fun claimPendingNFT(receiverID: UInt64, nftIndex: Int): @{NonFungibleToken.NFT} {
2267            let nft <- self.lotteryDistributor.claimPendingNFT(receiverID: receiverID, nftIndex: nftIndex)
2268            let nftType = nft.getType().identifier
2269            
2270            emit NFTPrizeClaimed(
2271                poolID: self.poolID,
2272                receiverID: receiverID,
2273                nftID: nft.uuid,
2274                nftType: nftType
2275            )
2276            
2277            return <- nft
2278        }
2279        
2280        access(all) fun canDrawNow(): Bool {
2281            return (getCurrentBlock().timestamp - self.lastDrawTimestamp) >= self.config.drawIntervalSeconds
2282        }
2283        
2284        access(all) fun getReceiverDeposit(receiverID: UInt64): UFix64 {
2285            return self.receiverDeposits[receiverID] ?? 0.0
2286        }
2287        
2288        access(all) fun getReceiverTotalEarnedSavings(receiverID: UInt64): UFix64 {
2289            return self.receiverTotalEarnedSavings[receiverID] ?? 0.0
2290        }
2291        
2292        access(all) fun getReceiverTotalEarnedPrizes(receiverID: UInt64): UFix64 {
2293            return self.receiverTotalEarnedPrizes[receiverID] ?? 0.0
2294        }
2295        
2296        access(all) fun getPendingSavingsInterest(receiverID: UInt64): UFix64 {
2297            let deposit = self.receiverDeposits[receiverID] ?? 0.0
2298            return self.savingsDistributor.calculatePendingInterest(receiverID: receiverID, deposit: deposit)
2299        }
2300        
2301        // Shares model inspection methods (useful for testing and debugging)
2302        access(all) fun getUserSavingsShares(receiverID: UInt64): UFix64 {
2303            return self.savingsDistributor.getUserShares(receiverID: receiverID)
2304        }
2305        
2306        access(all) fun getTotalSavingsShares(): UFix64 {
2307            return self.savingsDistributor.getTotalShares()
2308        }
2309        
2310        access(all) fun getTotalSavingsAssets(): UFix64 {
2311            return self.savingsDistributor.getTotalAssets()
2312        }
2313        
2314        access(all) fun getSavingsSharePrice(): UFix64 {
2315            let totalShares = self.savingsDistributor.getTotalShares()
2316            let totalAssets = self.savingsDistributor.getTotalAssets()
2317            return totalShares > 0.0 ? totalAssets / totalShares : 1.0
2318        }
2319        
2320        access(all) fun getUserSavingsValue(receiverID: UInt64): UFix64 {
2321            return self.savingsDistributor.getUserAssetValue(receiverID: receiverID)
2322        }
2323        
2324        access(all) fun isReceiverRegistered(receiverID: UInt64): Bool {
2325            return self.registeredReceivers[receiverID] == true
2326        }
2327        
2328        access(all) fun getRegisteredReceiverIDs(): [UInt64] {
2329            return self.registeredReceivers.keys
2330        }
2331        
2332        access(all) fun isDrawInProgress(): Bool {
2333            return self.pendingDrawReceipt != nil
2334        }
2335        
2336        access(all) fun getConfig(): PoolConfig {
2337            return self.config
2338        }
2339        
2340        access(all) fun getLiquidBalance(): UFix64 {
2341            return self.liquidVault.balance
2342        }
2343        
2344        access(all) fun getSavingsPoolBalance(): UFix64 {
2345            return self.savingsDistributor.getInterestVaultBalance()
2346        }
2347        
2348        access(all) fun getTotalSavingsDistributed(): UFix64 {
2349            return self.savingsDistributor.getTotalDistributed()
2350        }
2351        
2352        /// Calculate current amount of savings generating yield in the yield source
2353        /// This is the difference between totalStaked (all funds in yield source) and totalDeposited (user deposits)
2354        access(all) fun getCurrentReinvestedSavings(): UFix64 {
2355            if self.totalStaked > self.totalDeposited {
2356                return self.totalStaked - self.totalDeposited
2357            }
2358            return 0.0
2359        }
2360        
2361        /// Get available yield rewards ready to be collected
2362        /// This is the difference between what's in the yield source and what we've tracked as staked
2363        access(all) fun getAvailableYieldRewards(): UFix64 {
2364            let yieldSource = &self.config.yieldConnector as &{DeFiActions.Source}
2365            let available = yieldSource.minimumAvailable()
2366            if available > self.totalStaked {
2367                return available - self.totalStaked
2368            }
2369            return 0.0
2370        }
2371        
2372        access(all) fun getLotteryPoolBalance(): UFix64 {
2373            return self.lotteryDistributor.getPrizePoolBalance()
2374        }
2375        
2376        access(all) fun getTreasuryBalance(): UFix64 {
2377            return self.treasuryDistributor.getBalance()
2378        }
2379        
2380        access(all) fun getTreasuryStats(): {String: UFix64} {
2381            return {
2382                "balance": self.treasuryDistributor.getBalance(),
2383                "totalCollected": self.treasuryDistributor.getTotalCollected(),
2384                "totalWithdrawn": self.treasuryDistributor.getTotalWithdrawn()
2385            }
2386        }
2387        
2388        access(all) fun getTreasuryWithdrawalHistory(): [{String: AnyStruct}] {
2389            return self.treasuryDistributor.getWithdrawalHistory()
2390        }
2391        
2392        access(contract) fun withdrawTreasury(
2393            amount: UFix64,
2394            withdrawnBy: Address,
2395            purpose: String
2396        ): @{FungibleToken.Vault} {
2397            return <- self.treasuryDistributor.withdraw(
2398                amount: amount,
2399                withdrawnBy: withdrawnBy,
2400                purpose: purpose
2401            )
2402        }
2403        
2404    }
2405    
2406    // Pool Position Collection
2407    
2408    access(all) struct PoolBalance {
2409        access(all) let deposits: UFix64  // Total deposited (includes auto-compounded savings & prizes)
2410        access(all) let totalEarnedSavings: UFix64  // Historical: total savings earned (auto-compounded)
2411        access(all) let totalEarnedPrizes: UFix64  // Historical: total prizes earned (auto-compounded)
2412        access(all) let pendingSavings: UFix64  // Savings not yet compounded (ready to compound)
2413        access(all) let totalBalance: UFix64  // deposits + pendingSavings (total in yield sink)
2414        
2415        init(deposits: UFix64, totalEarnedSavings: UFix64, totalEarnedPrizes: UFix64, pendingSavings: UFix64) {
2416            self.deposits = deposits
2417            self.totalEarnedSavings = totalEarnedSavings
2418            self.totalEarnedPrizes = totalEarnedPrizes
2419            self.pendingSavings = pendingSavings
2420            self.totalBalance = deposits + pendingSavings
2421        }
2422    }
2423    
2424    access(all) resource interface PoolPositionCollectionPublic {
2425        access(all) fun getRegisteredPoolIDs(): [UInt64]
2426        access(all) fun isRegisteredWithPool(poolID: UInt64): Bool
2427        access(all) fun deposit(poolID: UInt64, from: @{FungibleToken.Vault})
2428        access(all) fun withdraw(poolID: UInt64, amount: UFix64): @{FungibleToken.Vault}
2429        access(all) fun compoundSavingsInterest(poolID: UInt64): UFix64
2430        access(all) fun getPoolBalance(poolID: UInt64): PoolBalance
2431    }
2432    
2433    access(all) resource PoolPositionCollection: PoolPositionCollectionPublic {
2434        access(self) let registeredPools: {UInt64: Bool}
2435        
2436        init() {
2437            self.registeredPools = {}
2438        }
2439        
2440        access(self) fun registerWithPool(poolID: UInt64) {
2441            pre {
2442                self.registeredPools[poolID] == nil: "Already registered"
2443            }
2444            
2445            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
2446                ?? panic("Pool does not exist")
2447            
2448            poolRef.registerReceiver(receiverID: self.uuid)
2449            self.registeredPools[poolID] = true
2450        }
2451        
2452        access(all) fun getRegisteredPoolIDs(): [UInt64] {
2453            return self.registeredPools.keys
2454        }
2455        
2456        access(all) fun isRegisteredWithPool(poolID: UInt64): Bool {
2457            return self.registeredPools[poolID] == true
2458        }
2459        
2460        access(all) fun deposit(poolID: UInt64, from: @{FungibleToken.Vault}) {
2461            if self.registeredPools[poolID] == nil {
2462                self.registerWithPool(poolID: poolID)
2463            }
2464            
2465            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
2466                ?? panic("Cannot borrow pool")
2467            
2468            poolRef.deposit(from: <- from, receiverID: self.uuid)
2469        }
2470        
2471        access(all) fun withdraw(poolID: UInt64, amount: UFix64): @{FungibleToken.Vault} {
2472            pre {
2473                self.registeredPools[poolID] == true: "Not registered with pool"
2474            }
2475            
2476            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
2477                ?? panic("Cannot borrow pool")
2478            
2479            return <- poolRef.withdraw(amount: amount, receiverID: self.uuid)
2480        }
2481        
2482        access(all) fun compoundSavingsInterest(poolID: UInt64): UFix64 {
2483            pre {
2484                self.registeredPools[poolID] == true: "Not registered with pool"
2485            }
2486            
2487            let poolRef = PrizeSavings.borrowPoolAuth(poolID: poolID)
2488                ?? panic("Cannot borrow pool")
2489            
2490            return poolRef.compoundSavingsInterest(receiverID: self.uuid)
2491        }
2492        
2493        access(all) fun getPoolBalance(poolID: UInt64): PoolBalance {
2494            if self.registeredPools[poolID] == nil {
2495                return PoolBalance(deposits: 0.0, totalEarnedSavings: 0.0, totalEarnedPrizes: 0.0, pendingSavings: 0.0)
2496            }
2497            
2498            let poolRef = PrizeSavings.borrowPool(poolID: poolID)
2499            if poolRef == nil {
2500                return PoolBalance(deposits: 0.0, totalEarnedSavings: 0.0, totalEarnedPrizes: 0.0, pendingSavings: 0.0)
2501            }
2502            
2503            return PoolBalance(
2504                deposits: poolRef!.getReceiverDeposit(receiverID: self.uuid),
2505                totalEarnedSavings: poolRef!.getReceiverTotalEarnedSavings(receiverID: self.uuid),
2506                totalEarnedPrizes: poolRef!.getReceiverTotalEarnedPrizes(receiverID: self.uuid),
2507                pendingSavings: poolRef!.getPendingSavingsInterest(receiverID: self.uuid)
2508            )
2509        }
2510    }
2511    
2512    // Contract Functions
2513    
2514    access(contract) fun createPool(
2515        config: PoolConfig,
2516        emergencyConfig: EmergencyConfig?,
2517        fundingPolicy: FundingPolicy?
2518    ): UInt64 {
2519        let emptyVault <- DeFiActionsUtils.getEmptyVault(config.assetType)
2520        let pool <- create Pool(
2521            config: config, 
2522            initialVault: <- emptyVault,
2523            emergencyConfig: emergencyConfig,
2524            fundingPolicy: fundingPolicy
2525        )
2526        
2527        let poolID = self.nextPoolID
2528        self.nextPoolID = self.nextPoolID + 1
2529        
2530        pool.setPoolID(id: poolID)
2531        emit PoolCreated(
2532            poolID: poolID,
2533            assetType: config.assetType.identifier,
2534            strategy: config.distributionStrategy.getStrategyName()
2535        )
2536        
2537        self.pools[poolID] <-! pool
2538        return poolID
2539    }
2540    
2541    access(all) view fun borrowPool(poolID: UInt64): &Pool? {
2542        return &self.pools[poolID]
2543    }
2544    
2545    /// Returns authorized pool reference - restricted to contract only for security
2546    /// Only Admin resource and internal contract functions can access this
2547    access(contract) fun borrowPoolAuth(poolID: UInt64): &Pool? {
2548        return &self.pools[poolID]
2549    }
2550    
2551    access(all) view fun getAllPoolIDs(): [UInt64] {
2552        return self.pools.keys
2553    }
2554    
2555    access(all) fun createPoolPositionCollection(): @PoolPositionCollection {
2556        return <- create PoolPositionCollection()
2557    }
2558    
2559    init() {
2560        self.PoolPositionCollectionStoragePath = /storage/PrizeSavingsCollection
2561        self.PoolPositionCollectionPublicPath = /public/PrizeSavingsCollection
2562        
2563        self.AdminStoragePath = /storage/PrizeSavingsAdmin
2564        self.AdminPublicPath = /public/PrizeSavingsAdmin
2565        
2566        self.pools <- {}
2567        self.nextPoolID = 0
2568        
2569        let admin <- create Admin()
2570        self.account.storage.save(<-admin, to: self.AdminStoragePath)
2571    }
2572}