Smart Contract

FlowWager

A.6c1b12e35dca8863.FlowWager

Valid From

118,667,506

Deployed

5d ago
Feb 22, 2026, 03:31:08 PM UTC

Dependents

13 imports
1access(all) contract FlowWager {
2    
3    // =====================================
4    // EVENTS
5    // =====================================
6    
7    access(all) event ContractInitialized()
8    access(all) event MarketCreated(marketId: UInt64, title: String, creator: Address)
9    access(all) event SharesPurchased(marketId: UInt64, buyer: Address, option: UInt8, shares: UFix64, amount: UFix64)
10    access(all) event MarketResolved(marketId: UInt64, outcome: UInt8)
11    access(all) event WinningsClaimed(marketId: UInt64, claimer: Address, amount: UFix64)
12    access(all) event UserRegistered(address: Address, username: String)
13    
14    
15    access(all) enum MarketCategory: UInt8 {
16        access(all) case Sports
17        access(all) case Entertainment
18        access(all) case Technology
19        access(all) case Economics
20        access(all) case Weather
21        access(all) case Crypto
22        access(all) case Politics
23        access(all) case BreakingNews
24        access(all) case Other
25    }
26    
27    access(all) enum MarketStatus: UInt8 {
28        access(all) case Active
29        access(all) case Paused
30        access(all) case Resolved
31        access(all) case Cancelled
32    }
33    
34    access(all) enum MarketOutcome: UInt8 {
35        access(all) case OptionA
36        access(all) case OptionB
37        access(all) case Draw
38        access(all) case Cancelled
39    }
40    
41    // =====================================
42    // STRUCTS - All fields immutable
43    // =====================================
44    
45    access(all) struct Market {
46        access(all) let id: UInt64
47        access(all) let title: String
48        access(all) let description: String
49        access(all) let category: MarketCategory
50        access(all) let optionA: String
51        access(all) let optionB: String
52        access(all) let creator: Address
53        access(all) let createdAt: UFix64
54        access(all) let endTime: UFix64
55        access(all) let minBet: UFix64
56        access(all) let maxBet: UFix64
57        access(all) let status: MarketStatus
58        access(all) let outcome: MarketOutcome?
59        access(all) let resolved: Bool
60        access(all) let totalOptionAShares: UFix64
61        access(all) let totalOptionBShares: UFix64
62        access(all) let totalPool: UFix64
63        
64        init(
65            id: UInt64,
66            title: String,
67            description: String,
68            category: MarketCategory,
69            optionA: String,
70            optionB: String,
71            creator: Address,
72            endTime: UFix64,
73            minBet: UFix64,
74            maxBet: UFix64,
75            status: MarketStatus,
76            outcome: MarketOutcome?,
77            resolved: Bool,
78            totalOptionAShares: UFix64,
79            totalOptionBShares: UFix64,
80            totalPool: UFix64
81        ) {
82            self.id = id
83            self.title = title
84            self.description = description
85            self.category = category
86            self.optionA = optionA
87            self.optionB = optionB
88            self.creator = creator
89            self.createdAt = getCurrentBlock().timestamp
90            self.endTime = endTime
91            self.minBet = minBet
92            self.maxBet = maxBet
93            self.status = status
94            self.outcome = outcome
95            self.resolved = resolved
96            self.totalOptionAShares = totalOptionAShares
97            self.totalOptionBShares = totalOptionBShares
98            self.totalPool = totalPool
99        }
100    }
101    
102    access(all) struct UserPosition {
103        access(all) let marketId: UInt64
104        access(all) let optionAShares: UFix64
105        access(all) let optionBShares: UFix64
106        access(all) let totalInvested: UFix64
107        access(all) let averagePrice: UFix64
108        access(all) let claimed: Bool
109        
110        init(marketId: UInt64, optionAShares: UFix64, optionBShares: UFix64, totalInvested: UFix64, claimed: Bool) {
111            self.marketId = marketId
112            self.optionAShares = optionAShares
113            self.optionBShares = optionBShares
114            self.totalInvested = totalInvested
115            self.averagePrice = totalInvested / (optionAShares + optionBShares)
116            self.claimed = claimed
117        }
118    }
119    
120    access(all) struct UserProfile {
121        access(all) let address: Address
122        access(all) let username: String
123        access(all) let joinedAt: UFix64
124        access(all) let displayName: String
125        access(all) let bio: String
126        access(all) let profileImageUrl: String
127        
128        init(address: Address, username: String, displayName: String, bio: String, profileImageUrl: String) {
129            self.address = address
130            self.username = username
131            self.displayName = displayName
132            self.joinedAt = getCurrentBlock().timestamp
133            self.bio = bio
134            self.profileImageUrl = profileImageUrl
135        }
136    }
137    
138    access(all) struct UserStats {
139        access(all) let totalMarketsParticipated: UInt64
140        access(all) let totalWinnings: UFix64
141        access(all) let totalLosses: UFix64
142        access(all) let winStreak: UInt64
143        access(all) let currentStreak: UInt64
144        access(all) let longestWinStreak: UInt64
145        access(all) let roi: UFix64
146        access(all) let averageBetSize: UFix64
147        
148        init(
149            totalMarketsParticipated: UInt64,
150            totalWinnings: UFix64,
151            totalLosses: UFix64,
152            winStreak: UInt64,
153            currentStreak: UInt64,
154            longestWinStreak: UInt64,
155            roi: UFix64,
156            averageBetSize: UFix64
157        ) {
158            self.totalMarketsParticipated = totalMarketsParticipated
159            self.totalWinnings = totalWinnings
160            self.totalLosses = totalLosses
161            self.winStreak = winStreak
162            self.currentStreak = currentStreak
163            self.longestWinStreak = longestWinStreak
164            self.roi = roi
165            self.averageBetSize = averageBetSize
166        }
167    }
168    
169    access(all) struct PlatformStats {
170        access(all) let totalMarkets: UInt64
171        access(all) let activeMarkets: UInt64
172        access(all) let totalUsers: UInt64
173        access(all) let totalVolume: UFix64
174        access(all) let totalFees: UFix64
175        
176        init(
177            totalMarkets: UInt64,
178            activeMarkets: UInt64,
179            totalUsers: UInt64,
180            totalVolume: UFix64,
181            totalFees: UFix64
182        ) {
183            self.totalMarkets = totalMarkets
184            self.activeMarkets = activeMarkets
185            self.totalUsers = totalUsers
186            self.totalVolume = totalVolume
187            self.totalFees = totalFees
188        }
189    }
190    
191    // =====================================
192    // CONTRACT STATE
193    // =====================================
194    
195    access(all) var nextMarketId: UInt64
196    access(all) var platformFeePercentage: UFix64
197    access(all) var totalPlatformFees: UFix64
198    access(all) var totalVolumeTraded: UFix64
199    
200    // Storage paths
201    access(all) let UserPositionsStoragePath: StoragePath
202    access(all) let UserPositionsPublicPath: PublicPath
203    access(all) let UserStatsStoragePath: StoragePath
204    access(all) let UserStatsPublicPath: PublicPath
205    
206    // Markets storage
207    access(contract) let markets: {UInt64: Market}
208    access(contract) let userProfiles: {Address: UserProfile}
209    access(contract) let userStats: {Address: UserStats}
210    
211    // =====================================
212    // RESOURCES
213    // =====================================
214    
215    access(all) resource UserPositions {
216        access(all) var positions: {UInt64: UserPosition}
217        
218        init() {
219            self.positions = {}
220        }
221        
222        access(all) fun addPosition(_ position: UserPosition) {
223            if let existingPosition = self.positions[position.marketId] {
224                let newPosition = UserPosition(
225                    marketId: position.marketId,
226                    optionAShares: existingPosition.optionAShares + position.optionAShares,
227                    optionBShares: existingPosition.optionBShares + position.optionBShares,
228                    totalInvested: existingPosition.totalInvested + position.totalInvested,
229                    claimed: false
230                )
231                self.positions[position.marketId] = newPosition
232            } else {
233                self.positions[position.marketId] = position
234            }
235        }
236        
237        access(all) fun getPosition(marketId: UInt64): UserPosition? {
238            return self.positions[marketId]
239        }
240        
241        access(all) fun getPositions(): {UInt64: UserPosition} {
242            return self.positions
243        }
244    }
245    
246    access(all) resource interface UserPositionsPublic {
247        access(all) fun getPosition(marketId: UInt64): UserPosition?
248        access(all) fun getPositions(): {UInt64: UserPosition}
249    }
250    
251    access(all) resource UserStatsResource {
252        access(all) var stats: UserStats
253        
254        init() {
255            self.stats = UserStats(
256                totalMarketsParticipated: 0,
257                totalWinnings: 0.0,
258                totalLosses: 0.0,
259                winStreak: 0,
260                currentStreak: 0,
261                longestWinStreak: 0,
262                roi: 0.0,
263                averageBetSize: 0.0
264            )
265        }
266        
267        access(all) fun getStats(): UserStats {
268            return self.stats
269        }
270    }
271    
272    access(all) resource interface UserStatsPublic {
273        access(all) fun getStats(): UserStats
274    }
275    
276    access(all) resource Admin {
277        access(all) fun createMarket(
278            title: String,
279            description: String,
280            category: MarketCategory,
281            optionA: String,
282            optionB: String,
283            endTime: UFix64,
284            minBet: UFix64,
285            maxBet: UFix64
286        ): UInt64 {
287            let marketId = FlowWager.nextMarketId
288            let market = Market(
289                id: marketId,
290                title: title,
291                description: description,
292                category: category,
293                optionA: optionA,
294                optionB: optionB,
295                creator: self.owner!.address,
296                endTime: endTime,
297                minBet: minBet,
298                maxBet: maxBet,
299                status: MarketStatus.Active,
300                outcome: nil,
301                resolved: false,
302                totalOptionAShares: 0.0,
303                totalOptionBShares: 0.0,
304                totalPool: 0.0
305            )
306            
307            FlowWager.markets[marketId] = market
308            FlowWager.nextMarketId = FlowWager.nextMarketId + 1
309            
310            emit MarketCreated(marketId: marketId, title: title, creator: self.owner!.address)
311            return marketId
312        }
313        
314        access(all) fun resolveMarket(marketId: UInt64, outcome: MarketOutcome) {
315            pre {
316                FlowWager.markets[marketId] != nil: "Market does not exist"
317                FlowWager.markets[marketId]!.resolved == false: "Market already resolved"
318            }
319            
320            if let currentMarket = FlowWager.markets[marketId] {
321                // Create new market instance with updated values
322                let resolvedMarket = Market(
323                    id: currentMarket.id,
324                    title: currentMarket.title,
325                    description: currentMarket.description,
326                    category: currentMarket.category,
327                    optionA: currentMarket.optionA,
328                    optionB: currentMarket.optionB,
329                    creator: currentMarket.creator,
330                    endTime: currentMarket.endTime,
331                    minBet: currentMarket.minBet,
332                    maxBet: currentMarket.maxBet,
333                    status: MarketStatus.Resolved,
334                    outcome: outcome,
335                    resolved: true,
336                    totalOptionAShares: currentMarket.totalOptionAShares,
337                    totalOptionBShares: currentMarket.totalOptionBShares,
338                    totalPool: currentMarket.totalPool
339                )
340                
341                FlowWager.markets[marketId] = resolvedMarket
342                emit MarketResolved(marketId: marketId, outcome: outcome.rawValue)
343            }
344        }
345        
346        access(all) fun updatePlatformFee(newFeePercentage: UFix64) {
347            pre {
348                newFeePercentage >= 0.0 && newFeePercentage <= 10.0: "Fee must be between 0% and 10%"
349            }
350            FlowWager.platformFeePercentage = newFeePercentage
351        }
352    }
353    
354    // =====================================
355    // PUBLIC FUNCTIONS
356    // =====================================
357    
358    access(all) fun createUserAccount(username: String, displayName: String) {
359        pre {
360            username.length > 0: "Username cannot be empty"
361            displayName.length > 0: "Display name cannot be empty"
362        }
363        
364        let userProfile = UserProfile(
365            address: self.account.address,
366            username: username,
367            displayName: displayName,
368            bio: "",
369            profileImageUrl: ""
370        )
371        
372        let userStatsData = UserStats(
373            totalMarketsParticipated: 0,
374            totalWinnings: 0.0,
375            totalLosses: 0.0,
376            winStreak: 0,
377            currentStreak: 0,
378            longestWinStreak: 0,
379            roi: 0.0,
380            averageBetSize: 0.0
381        )
382        
383        FlowWager.userProfiles[self.account.address] = userProfile
384        FlowWager.userStats[self.account.address] = userStatsData
385        
386        emit UserRegistered(address: self.account.address, username: username)
387    }
388    
389    access(all) fun buyShares(
390        marketId: UInt64,
391        option: UInt8,
392        amount: UFix64
393    ) {
394        pre {
395            option == 0 || option == 1: "Option must be 0 (Option A) or 1 (Option B)"
396            FlowWager.markets[marketId] != nil: "Market does not exist"
397            FlowWager.markets[marketId]!.status == MarketStatus.Active: "Market is not active"
398            FlowWager.markets[marketId]!.endTime > getCurrentBlock().timestamp: "Market has ended"
399            amount >= FlowWager.markets[marketId]!.minBet: "Amount below minimum bet"
400            amount <= FlowWager.markets[marketId]!.maxBet: "Amount above maximum bet"
401        }
402        
403        let betAmount = amount
404        let platformFee = betAmount * FlowWager.platformFeePercentage / 100.0
405        let netAmount = betAmount - platformFee
406        let shares = netAmount
407        
408        // Update platform totals
409        FlowWager.totalPlatformFees = FlowWager.totalPlatformFees + platformFee
410        FlowWager.totalVolumeTraded = FlowWager.totalVolumeTraded + betAmount
411        
412        // Update market by creating new instance
413        if let currentMarket = FlowWager.markets[marketId] {
414            let updatedMarket = Market(
415                id: currentMarket.id,
416                title: currentMarket.title,
417                description: currentMarket.description,
418                category: currentMarket.category,
419                optionA: currentMarket.optionA,
420                optionB: currentMarket.optionB,
421                creator: currentMarket.creator,
422                endTime: currentMarket.endTime,
423                minBet: currentMarket.minBet,
424                maxBet: currentMarket.maxBet,
425                status: currentMarket.status,
426                outcome: currentMarket.outcome,
427                resolved: currentMarket.resolved,
428                totalOptionAShares: option == 0 ? currentMarket.totalOptionAShares + shares : currentMarket.totalOptionAShares,
429                totalOptionBShares: option == 1 ? currentMarket.totalOptionBShares + shares : currentMarket.totalOptionBShares,
430                totalPool: currentMarket.totalPool + netAmount
431            )
432            
433            FlowWager.markets[marketId] = updatedMarket
434        }
435        
436        // Track individual user position
437        let userAddress = self.account.address
438        let newPosition = UserPosition(
439            marketId: marketId,
440            optionAShares: option == 0 ? shares : 0.0,
441            optionBShares: option == 1 ? shares : 0.0,
442            totalInvested: betAmount,
443            claimed: false
444        )
445        
446        // Store user position in user's account storage
447        let userPositionsRef = self.account.storage.borrow<&UserPositions>(from: FlowWager.UserPositionsStoragePath)
448        if userPositionsRef != nil {
449            userPositionsRef!.addPosition(newPosition)
450        } else {
451            // Create new UserPositions resource if it doesn't exist
452            let userPositions <- create UserPositions()
453            userPositions.addPosition(newPosition)
454            self.account.storage.save(<-userPositions, to: FlowWager.UserPositionsStoragePath)
455            
456            // Link capability
457            self.account.capabilities.publish(
458                self.account.capabilities.storage.issue<&UserPositions>(FlowWager.UserPositionsStoragePath),
459                at: FlowWager.UserPositionsPublicPath
460            )
461        }
462        
463        emit SharesPurchased(
464            marketId: marketId,
465            buyer: self.account.address,
466            option: option,
467            shares: shares,
468            amount: betAmount
469        )
470    }
471    
472    // =====================================
473    // READ FUNCTIONS
474    // =====================================
475    
476    access(all) fun getMarket(marketId: UInt64): Market? {
477        return FlowWager.markets[marketId]
478    }
479    
480    access(all) fun getAllMarkets(): [Market] {
481        return FlowWager.markets.values
482    }
483    
484    access(all) fun getActiveMarkets(): [Market] {
485        let activeMarkets: [Market] = []
486        for market in FlowWager.markets.values {
487            if market.status == MarketStatus.Active {
488                activeMarkets.append(market)
489            }
490        }
491        return activeMarkets
492    }
493    
494    access(all) fun getMarketsByCategory(category: MarketCategory): [Market] {
495        let filteredMarkets: [Market] = []
496        for market in FlowWager.markets.values {
497            if market.category == category {
498                filteredMarkets.append(market)
499            }
500        }
501        return filteredMarkets
502    }
503    
504    access(all) fun getUserProfile(address: Address): UserProfile? {
505        return FlowWager.userProfiles[address]
506    }
507    
508    access(all) fun getUserStats(address: Address): UserStats? {
509        return FlowWager.userStats[address]
510    }
511    
512    access(all) fun getPlatformStats(): PlatformStats {
513        var activeMarkets: UInt64 = 0
514        for market in FlowWager.markets.values {
515            if market.status == MarketStatus.Active {
516                activeMarkets = activeMarkets + 1
517            }
518        }
519        
520        return PlatformStats(
521            totalMarkets: UInt64(FlowWager.markets.length),
522            activeMarkets: activeMarkets,
523            totalUsers: UInt64(FlowWager.userProfiles.length),
524            totalVolume: FlowWager.totalVolumeTraded,
525            totalFees: FlowWager.totalPlatformFees
526        )
527    }
528    
529    // =====================================
530    // CONTRACT INITIALIZATION
531    // =====================================
532    
533    init() {
534        self.nextMarketId = 1
535        self.platformFeePercentage = 3.0
536        self.totalPlatformFees = 0.0
537        self.totalVolumeTraded = 0.0
538        
539        self.markets = {}
540        self.userProfiles = {}
541        self.userStats = {}
542        
543        self.UserPositionsStoragePath = /storage/FlowWagerUserPositions
544        self.UserPositionsPublicPath = /public/FlowWagerUserPositions
545        self.UserStatsStoragePath = /storage/FlowWagerUserStats
546        self.UserStatsPublicPath = /public/FlowWagerUserStats
547        
548        let admin <- create Admin()
549        self.account.storage.save(<-admin, to: /storage/FlowWagerAdmin)
550        
551        emit ContractInitialized()
552    }
553}