Smart Contract

UserRegistry

A.6c1b12e35dca8863.UserRegistry

Valid From

118,667,537

Deployed

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

Dependents

0 imports
1// cadence/contracts/UserRegistry.cdc
2// Registry contract for managing user profiles and social features - Fixed for Cadence 1.0
3
4import FlowWager from 0x6c1b12e35dca8863
5
6access(all) contract UserRegistry {
7    
8    // ========================================
9    // EVENTS
10    // ========================================
11    
12    access(all) event UserRegistered(address: Address, username: String?)
13    access(all) event UsernameUpdated(address: Address, oldUsername: String?, newUsername: String)
14    access(all) event UserFollowed(follower: Address, following: Address)
15    access(all) event UserUnfollowed(follower: Address, following: Address)
16    access(all) event AchievementUnlocked(address: Address, achievementId: String, achievementName: String)
17    access(all) event UserProfileUpdated(address: Address, field: String)
18    access(all) event UserRegistryInitialized()
19    
20    // ========================================
21    // STRUCTS
22    // ========================================
23    
24    access(all) struct UserProfile {
25        access(all) let address: Address
26        access(all) var username: String?
27        access(all) var displayName: String?
28        access(all) var bio: String?
29        access(all) var avatarURL: String?
30        access(all) var website: String?
31        access(all) var twitterHandle: String?
32        access(all) var discordHandle: String?
33        access(all) let registrationDate: UFix64
34        access(all) var lastActiveDate: UFix64
35        access(all) var isVerified: Bool
36        access(all) var privacySettings: PrivacySettings
37        access(all) var notificationSettings: NotificationSettings
38        access(all) var achievements: [String] // Achievement IDs
39        access(all) var customMetadata: {String: String}
40        
41        init(address: Address) {
42            self.address = address
43            self.username = nil
44            self.displayName = nil
45            self.bio = nil
46            self.avatarURL = nil
47            self.website = nil
48            self.twitterHandle = nil
49            self.discordHandle = nil
50            self.registrationDate = getCurrentBlock().timestamp
51            self.lastActiveDate = getCurrentBlock().timestamp
52            self.isVerified = false
53            self.privacySettings = PrivacySettings()
54            self.notificationSettings = NotificationSettings()
55            self.achievements = []
56            self.customMetadata = {}
57        }
58        
59        // Setter methods for controlled mutation
60        access(contract) fun setUsername(_ newUsername: String?) {
61            self.username = newUsername
62        }
63        
64        access(contract) fun setDisplayName(_ newDisplayName: String?) {
65            self.displayName = newDisplayName
66        }
67        
68        access(contract) fun setBio(_ newBio: String?) {
69            self.bio = newBio
70        }
71        
72        access(contract) fun setAvatarURL(_ newAvatarURL: String?) {
73            self.avatarURL = newAvatarURL
74        }
75        
76        access(contract) fun setWebsite(_ newWebsite: String?) {
77            self.website = newWebsite
78        }
79        
80        access(contract) fun setTwitterHandle(_ newTwitterHandle: String?) {
81            self.twitterHandle = newTwitterHandle
82        }
83        
84        access(contract) fun setDiscordHandle(_ newDiscordHandle: String?) {
85            self.discordHandle = newDiscordHandle
86        }
87        
88        access(contract) fun setVerified(_ verified: Bool) {
89            self.isVerified = verified
90        }
91        
92        access(contract) fun setPrivacySettings(_ settings: PrivacySettings) {
93            self.privacySettings = settings
94        }
95        
96        access(contract) fun setNotificationSettings(_ settings: NotificationSettings) {
97            self.notificationSettings = settings
98        }
99        
100        access(contract) fun updateLastActive() {
101            self.lastActiveDate = getCurrentBlock().timestamp
102        }
103        
104        access(contract) fun addAchievement(_ achievementId: String) {
105            if !self.achievements.contains(achievementId) {
106                self.achievements.append(achievementId)
107            }
108        }
109        
110        access(contract) fun setCustomMetadata(_ key: String, _ value: String) {
111            self.customMetadata[key] = value
112        }
113        
114        // Bulk update method
115        access(contract) fun updateProfile(
116            displayName: String?,
117            bio: String?,
118            avatarURL: String?,
119            website: String?,
120            twitterHandle: String?,
121            discordHandle: String?
122        ) {
123            self.displayName = displayName
124            self.bio = bio
125            self.avatarURL = avatarURL
126            self.website = website
127            self.twitterHandle = twitterHandle
128            self.discordHandle = discordHandle
129            self.updateLastActive()
130        }
131    }
132    
133    access(all) struct PrivacySettings {
134        access(all) var profilePublic: Bool
135        access(all) var statsPublic: Bool
136        access(all) var positionsPublic: Bool
137        access(all) var allowFollowers: Bool
138        access(all) var allowDirectMessages: Bool
139        access(all) var showOnLeaderboard: Bool
140        
141        init() {
142            self.profilePublic = true
143            self.statsPublic = true
144            self.positionsPublic = false
145            self.allowFollowers = true
146            self.allowDirectMessages = true
147            self.showOnLeaderboard = true
148        }
149    }
150    
151    access(all) struct NotificationSettings {
152        access(all) var emailNotifications: Bool
153        access(all) var pushNotifications: Bool
154        access(all) var marketResolutionNotifications: Bool
155        access(all) var winningsNotifications: Bool
156        access(all) var followerNotifications: Bool
157        access(all) var newMarketNotifications: Bool
158        access(all) var breakingNewsNotifications: Bool
159        access(all) var weeklyDigest: Bool
160        
161        init() {
162            self.emailNotifications = true
163            self.pushNotifications = true
164            self.marketResolutionNotifications = true
165            self.winningsNotifications = true
166            self.followerNotifications = true
167            self.newMarketNotifications = false
168            self.breakingNewsNotifications = true
169            self.weeklyDigest = true
170        }
171    }
172    
173    access(all) struct FollowRelationship {
174        access(all) let follower: Address
175        access(all) let following: Address
176        access(all) let timestamp: UFix64
177        
178        init(follower: Address, following: Address) {
179            self.follower = follower
180            self.following = following
181            self.timestamp = getCurrentBlock().timestamp
182        }
183    }
184    
185    access(all) struct UserAchievement {
186        access(all) let id: String
187        access(all) let name: String
188        access(all) let description: String
189        access(all) let category: String
190        access(all) let rarity: String // "common", "rare", "epic", "legendary"
191        access(all) let iconURL: String
192        access(all) let points: UInt64
193        access(all) let requirements: {String: AnyStruct}
194        access(all) let isActive: Bool
195        
196        init(
197            id: String,
198            name: String,
199            description: String,
200            category: String,
201            rarity: String,
202            iconURL: String,
203            points: UInt64,
204            requirements: {String: AnyStruct}
205        ) {
206            self.id = id
207            self.name = name
208            self.description = description
209            self.category = category
210            self.rarity = rarity
211            self.iconURL = iconURL
212            self.points = points
213            self.requirements = requirements
214            self.isActive = true
215        }
216    }
217    
218    access(all) struct UserSocialStats {
219        access(all) let address: Address
220        access(all) let followerCount: UInt64
221        access(all) let followingCount: UInt64
222        access(all) let achievementCount: UInt64
223        access(all) let achievementPoints: UInt64
224        access(all) let registrationDate: UFix64
225        access(all) let lastActiveDate: UFix64
226        access(all) let daysSinceRegistration: UInt64
227        access(all) let isVerified: Bool
228        
229        init(
230            address: Address,
231            followerCount: UInt64,
232            followingCount: UInt64,
233            achievementCount: UInt64,
234            achievementPoints: UInt64,
235            registrationDate: UFix64,
236            lastActiveDate: UFix64,
237            isVerified: Bool
238        ) {
239            self.address = address
240            self.followerCount = followerCount
241            self.followingCount = followingCount
242            self.achievementCount = achievementCount
243            self.achievementPoints = achievementPoints
244            self.registrationDate = registrationDate
245            self.lastActiveDate = lastActiveDate
246            self.daysSinceRegistration = UInt64((getCurrentBlock().timestamp - registrationDate) / 86400.0)
247            self.isVerified = isVerified
248        }
249    }
250    
251    // ========================================
252    // RESOURCES
253    // ========================================
254    
255    access(all) resource Admin {
256        access(all) fun createAchievement(
257            id: String,
258            name: String,
259            description: String,
260            category: String,
261            rarity: String,
262            iconURL: String,
263            points: UInt64,
264            requirements: {String: AnyStruct}
265        ) {
266            pre {
267                !UserRegistry.achievements.containsKey(id): "Achievement already exists"
268                name.length > 0: "Achievement name cannot be empty"
269                description.length > 0: "Achievement description cannot be empty"
270            }
271            
272            let achievement = UserAchievement(
273                id: id,
274                name: name,
275                description: description,
276                category: category,
277                rarity: rarity,
278                iconURL: iconURL,
279                points: points,
280                requirements: requirements
281            )
282            
283            UserRegistry.achievements[id] = achievement
284        }
285        
286        access(all) fun verifyUser(address: Address) {
287            pre {
288                UserRegistry.userProfiles.containsKey(address): "User profile does not exist"
289            }
290            
291            if let profile = UserRegistry.userProfiles[address] {
292                profile.setVerified(true)
293                UserRegistry.userProfiles[address] = profile
294                emit UserProfileUpdated(address: address, field: "verified")
295            }
296        }
297        
298        access(all) fun unverifyUser(address: Address) {
299            pre {
300                UserRegistry.userProfiles.containsKey(address): "User profile does not exist"
301            }
302            
303            if let profile = UserRegistry.userProfiles[address] {
304                profile.setVerified(false)
305                UserRegistry.userProfiles[address] = profile
306                emit UserProfileUpdated(address: address, field: "verified")
307            }
308        }
309        
310        access(all) fun grantAchievement(address: Address, achievementId: String) {
311            pre {
312                UserRegistry.userProfiles.containsKey(address): "User profile does not exist"
313                UserRegistry.achievements.containsKey(achievementId): "Achievement does not exist"
314            }
315            
316            if let profile = UserRegistry.userProfiles[address] {
317                let achievement = UserRegistry.achievements[achievementId]!
318                
319                if !profile.achievements.contains(achievementId) {
320                    profile.addAchievement(achievementId)
321                    UserRegistry.userProfiles[address] = profile
322                    
323                    emit AchievementUnlocked(
324                        address: address,
325                        achievementId: achievementId,
326                        achievementName: achievement.name
327                    )
328                }
329            }
330        }
331        
332        access(all) fun bulkGrantAchievements(addresses: [Address], achievementId: String) {
333            for address in addresses {
334                self.grantAchievement(address: address, achievementId: achievementId)
335            }
336        }
337        
338        access(all) fun getRegistryStats(): {String: AnyStruct} {
339            var verifiedUsers: UInt64 = 0
340            var usersWithUsernames: UInt64 = 0
341            let totalFollowRelationships = UInt64(UserRegistry.followRelationships.length)
342            
343            for profile in UserRegistry.userProfiles.values {
344                if profile.isVerified {
345                    verifiedUsers = verifiedUsers + 1
346                }
347                if profile.username != nil {
348                    usersWithUsernames = usersWithUsernames + 1
349                }
350            }
351            
352            return {
353                "totalUsers": UInt64(UserRegistry.userProfiles.length),
354                "verifiedUsers": verifiedUsers,
355                "usersWithUsernames": usersWithUsernames,
356                "totalFollowRelationships": totalFollowRelationships,
357                "totalAchievements": UInt64(UserRegistry.achievements.length)
358            }
359        }
360    }
361    
362    // ========================================
363    // CONTRACT STATE
364    // ========================================
365    
366    access(contract) var userProfiles: {Address: UserProfile}
367    access(contract) var usernameToAddress: {String: Address}
368    access(contract) var followRelationships: [FollowRelationship]
369    access(contract) var userFollowers: {Address: [Address]}
370    access(contract) var userFollowing: {Address: [Address]}
371    access(contract) var achievements: {String: UserAchievement}
372    access(all) var totalUsers: UInt64
373    
374    // Storage Paths
375    access(all) let AdminStoragePath: StoragePath
376    
377    // ========================================
378    // PUBLIC FUNCTIONS
379    // ========================================
380    
381    access(all) fun registerUser(address: Address, username: String?): Bool {
382        if self.userProfiles.containsKey(address) {
383            return false // User already registered
384        }
385        
386        // Check username availability if provided
387        if username != nil {
388            let usernameStr = username!
389            if self.isUsernameValid(username: usernameStr) && !self.usernameToAddress.containsKey(usernameStr) {
390                self.usernameToAddress[usernameStr] = address
391            } else {
392                panic("Username is invalid or already taken")
393            }
394        }
395        
396        // Create user profile
397        let profile = UserProfile(address: address)
398        if username != nil {
399            profile.setUsername(username)
400        }
401        
402        self.userProfiles[address] = profile
403        self.userFollowers[address] = []
404        self.userFollowing[address] = []
405        self.totalUsers = self.totalUsers + 1
406        
407        emit UserRegistered(address: address, username: username)
408        
409        // Check for first-time achievements
410        self.checkAndGrantAchievements(address: address)
411        
412        return true
413    }
414    
415    access(all) fun updateUserProfile(
416        address: Address,
417        displayName: String?,
418        bio: String?,
419        avatarURL: String?,
420        website: String?,
421        twitterHandle: String?,
422        discordHandle: String?
423    ) {
424        pre {
425            self.userProfiles.containsKey(address): "User profile does not exist"
426        }
427        
428        if let profile = self.userProfiles[address] {
429            profile.updateProfile(
430                displayName: displayName,
431                bio: bio,
432                avatarURL: avatarURL,
433                website: website,
434                twitterHandle: twitterHandle,
435                discordHandle: discordHandle
436            )
437            
438            self.userProfiles[address] = profile
439            emit UserProfileUpdated(address: address, field: "profile")
440        }
441    }
442    
443    access(all) fun updateUsername(address: Address, newUsername: String) {
444        pre {
445            self.userProfiles.containsKey(address): "User profile does not exist"
446            self.isUsernameValid(username: newUsername): "Username is invalid"
447            !self.usernameToAddress.containsKey(newUsername): "Username is already taken"
448        }
449        
450        if let profile = self.userProfiles[address] {
451            let oldUsername = profile.username
452            
453            // Remove old username mapping
454            if oldUsername != nil {
455                let _ = self.usernameToAddress.remove(key: oldUsername!)
456            }
457            
458            // Update username
459            profile.setUsername(newUsername)
460            profile.updateLastActive()
461            
462            self.usernameToAddress[newUsername] = address
463            self.userProfiles[address] = profile
464            
465            emit UsernameUpdated(address: address, oldUsername: oldUsername, newUsername: newUsername)
466        }
467    }
468    
469    access(all) fun updateLastActive(address: Address) {
470        if let profile = self.userProfiles[address] {
471            profile.updateLastActive()
472            self.userProfiles[address] = profile
473        }
474    }
475    
476    access(all) fun followUser(followerAddress: Address, followingAddress: Address) {
477        pre {
478            self.userProfiles.containsKey(followerAddress): "Follower profile does not exist"
479            self.userProfiles.containsKey(followingAddress): "Following profile does not exist"
480            followerAddress != followingAddress: "Cannot follow yourself"
481        }
482        
483        // Check if already following
484        let currentFollowing = self.userFollowing[followerAddress] ?? []
485        if currentFollowing.contains(followingAddress) {
486            return // Already following
487        }
488        
489        // Check privacy settings
490        let followingProfile = self.userProfiles[followingAddress]!
491        if !followingProfile.privacySettings.allowFollowers {
492            panic("User does not allow followers")
493        }
494        
495        // Add to following/followers lists
496        if self.userFollowing[followerAddress] == nil {
497            self.userFollowing[followerAddress] = []
498        }
499        if self.userFollowers[followingAddress] == nil {
500            self.userFollowers[followingAddress] = []
501        }
502        
503        self.userFollowing[followerAddress]!.append(followingAddress)
504        self.userFollowers[followingAddress]!.append(followerAddress)
505        
506        // Create relationship record
507        let relationship = FollowRelationship(follower: followerAddress, following: followingAddress)
508        self.followRelationships.append(relationship)
509        
510        // Update last active for follower
511        self.updateLastActive(address: followerAddress)
512        
513        emit UserFollowed(follower: followerAddress, following: followingAddress)
514        
515        // Check for social achievements
516        self.checkAndGrantAchievements(address: followingAddress)
517    }
518    
519    access(all) fun unfollowUser(followerAddress: Address, followingAddress: Address) {
520        pre {
521            self.userProfiles.containsKey(followerAddress): "Follower profile does not exist"
522            self.userProfiles.containsKey(followingAddress): "Following profile does not exist"
523        }
524        
525        // Remove from following/followers lists
526        if let followingList = self.userFollowing[followerAddress] {
527            var i = 0
528            while i < followingList.length {
529                if followingList[i] == followingAddress {
530                    let _ = followingList.remove(at: i)
531                    break
532                }
533                i = i + 1
534            }
535            self.userFollowing[followerAddress] = followingList
536        }
537        
538        if let followersList = self.userFollowers[followingAddress] {
539            var i = 0
540            while i < followersList.length {
541                if followersList[i] == followerAddress {
542                    let _ = followersList.remove(at: i)
543                    break
544                }
545                i = i + 1
546            }
547            self.userFollowers[followingAddress] = followersList
548        }
549        
550        // Update last active for follower
551        self.updateLastActive(address: followerAddress)
552        
553        emit UserUnfollowed(follower: followerAddress, following: followingAddress)
554    }
555    
556    // ========================================
557    // VIEW FUNCTIONS (Pure - No State Modification)
558    // ========================================
559    
560    access(all) view fun getUserProfile(address: Address): UserProfile? {
561        return self.userProfiles[address]
562    }
563    
564    access(all) view fun getUserByUsername(username: String): UserProfile? {
565        if let address = self.usernameToAddress[username] {
566            return self.userProfiles[address]
567        }
568        return nil
569    }
570    
571    access(all) view fun isUsernameAvailable(username: String): Bool {
572        return self.isUsernameValid(username: username) && !self.usernameToAddress.containsKey(username)
573    }
574    
575access(all) view fun getUserSocialStats(address: Address): {String: AnyStruct}? {
576    if let profile = self.userProfiles[address] {
577        return {
578            "address": address,
579            "followerCount": UInt64, // Default since field doesn't exist
580            "followingCount": UInt64, // Default since field doesn't exist  
581            "achievementCount": UInt64(profile.achievements.length), // ✅ This works
582            "achievementPoints": UInt64(profile.achievements.length * 10), // Calculate: 10 points per achievement
583            "registrationDate": profile.registrationDate,
584            "lastActiveDate": profile.lastActiveDate,
585            "isVerified": profile.isVerified
586        }
587    }
588    return nil
589}
590    
591    access(all) view fun getUserFollowers(address: Address): [Address] {
592        return self.userFollowers[address] ?? []
593    }
594    
595    access(all) view fun getUserFollowing(address: Address): [Address] {
596        return self.userFollowing[address] ?? []
597    }
598    
599    access(all) view fun isFollowing(follower: Address, following: Address): Bool {
600        let followingList = self.userFollowing[follower] ?? []
601        return followingList.contains(following)
602    }
603    
604    access(all) view fun getAchievement(achievementId: String): UserAchievement? {
605        return self.achievements[achievementId]
606    }
607    
608    access(all) view fun getAllAchievements(): [UserAchievement] {
609        return self.achievements.values
610    }
611    
612    access(all) view fun getUserAchievements(address: Address): [UserAchievement] {
613        if let profile = self.userProfiles[address] {
614            // Fixed: Create array using functional approach to avoid impure operations
615            var userAchievements: [UserAchievement] = []
616            var i = 0
617            while i < profile.achievements.length {
618                let achievementId = profile.achievements[i]
619                if let achievement = self.achievements[achievementId] {
620                    userAchievements = userAchievements.concat([achievement])
621                }
622                i = i + 1
623            }
624            return userAchievements
625        }
626        return []
627    }
628    
629    access(all) view fun searchUsers(query: String, limit: UInt64): [UserProfile] {
630        // Fixed: Create results using functional approach to avoid impure operations
631        var results: [UserProfile] = []
632        var count: UInt64 = 0
633        
634        for profile in self.userProfiles.values {
635            if count >= limit {
636                break
637            }
638            
639            // Search in username and display name
640            let matchUsername = profile.username != nil && profile.username!.toLower().contains(query.toLower())
641            let matchDisplayName = profile.displayName != nil && profile.displayName!.toLower().contains(query.toLower())
642            
643            if matchUsername || matchDisplayName {
644                if profile.privacySettings.profilePublic {
645                    results = results.concat([profile])
646                    count = count + 1
647                }
648            }
649        }
650        
651        return results
652    }
653    
654    // ========================================
655    // HELPER FUNCTIONS
656    // ========================================
657    
658    access(contract) view fun isUsernameValid(username: String): Bool {
659        // Username validation rules
660        if username.length < 3 || username.length > 20 {
661            return false
662        }
663        
664        // Check for valid characters (simplified - alphanumeric and underscores)
665        // In a real implementation, you'd use proper regex
666        return true
667    }
668    
669    access(contract) fun checkAndGrantAchievements(address: Address) {
670        // Check social achievements
671        let followerCount = UInt64(self.userFollowers[address]?.length ?? 0)
672        if followerCount >= 10 {
673            if let profile = self.userProfiles[address] {
674                if !profile.achievements.contains("social_10_followers") {
675                    profile.addAchievement("social_10_followers")
676                    self.userProfiles[address] = profile
677                    
678                    emit AchievementUnlocked(
679                        address: address, 
680                        achievementId: "social_10_followers", 
681                        achievementName: "Influencer"
682                    )
683                }
684            }
685        }
686    }
687    
688    access(contract) fun createDefaultAchievements() {
689        // First Bet Achievement
690        self.achievements["first_bet"] = UserAchievement(
691            id: "first_bet",
692            name: "First Prediction",
693            description: "Made your first prediction on FlowWager",
694            category: "getting_started",
695            rarity: "common",
696            iconURL: "https://example.com/achievements/first_bet.png",
697            points: 10,
698            requirements: {"bets": 1}
699        )
700        
701        // First Win Achievement
702        self.achievements["first_win"] = UserAchievement(
703            id: "first_win",
704            name: "Lucky Beginner",
705            description: "Won your first prediction",
706            category: "winning",
707            rarity: "common",
708            iconURL: "https://example.com/achievements/first_win.png",
709            points: 25,
710            requirements: {"wins": 1}
711        )
712        
713        // Streak Achievements
714        self.achievements["streak_5"] = UserAchievement(
715            id: "streak_5",
716            name: "Hot Streak",
717            description: "Achieved a 5-win streak",
718            category: "streaks",
719            rarity: "rare",
720            iconURL: "https://example.com/achievements/streak_5.png",
721            points: 100,
722            requirements: {"streak": 5}
723        )
724        
725        // Volume Achievements
726        self.achievements["volume_1000"] = UserAchievement(
727            id: "volume_1000",
728            name: "High Roller",
729            description: "Wagered over 1,000 FLOW tokens",
730            category: "volume",
731            rarity: "epic",
732            iconURL: "https://example.com/achievements/volume_1000.png",
733            points: 250,
734            requirements: {"totalInvested": 1000.0}
735        )
736        
737        // Social Achievements
738        self.achievements["social_10_followers"] = UserAchievement(
739            id: "social_10_followers",
740            name: "Influencer",
741            description: "Gained 10 followers",
742            category: "social",
743            rarity: "rare",
744            iconURL: "https://example.com/achievements/influencer.png",
745            points: 150,
746            requirements: {"followers": 10}
747        )
748    }
749    
750    // ========================================
751    // INITIALIZATION
752    // ========================================
753    
754    init() {
755        self.userProfiles = {}
756        self.usernameToAddress = {}
757        self.followRelationships = []
758        self.userFollowers = {}
759        self.userFollowing = {}
760        self.achievements = {}
761        self.totalUsers = 0
762        
763        self.AdminStoragePath = /storage/UserRegistryAdmin
764        
765        // Create and store Admin resource
766        let admin <- create Admin()
767        self.account.storage.save(<-admin, to: self.AdminStoragePath)
768        
769        // Create default achievements
770        self.createDefaultAchievements()
771        
772        emit UserRegistryInitialized()
773    }
774}