From a9741cf5d2fbbfe00d1943130f24de94c58b7fcc Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Tue, 21 Oct 2025 10:14:18 +0200 Subject: [PATCH 01/25] feat: add geo-resto package --- packages/r/karma1337/geo-resto/README.md | 66 +++ packages/r/karma1337/geo-resto/auth.gno | 268 +++++++++++ packages/r/karma1337/geo-resto/auth_test.gno | 110 +++++ packages/r/karma1337/geo-resto/event.gno | 448 ++++++++++++++++++ packages/r/karma1337/geo-resto/event_test.gno | 211 +++++++++ packages/r/karma1337/geo-resto/geo_resto.gno | 162 +++++++ packages/r/karma1337/geo-resto/gnomod.toml | 2 + packages/r/karma1337/geo-resto/location.gno | 164 +++++++ .../r/karma1337/geo-resto/location_test.gno | 142 ++++++ .../geo-resto/qr_verification_test.gno | 131 +++++ packages/r/karma1337/geo-resto/renderer.gno | 430 +++++++++++++++++ packages/r/karma1337/geo-resto/visit.gno | 217 +++++++++ packages/r/karma1337/geo-resto/visit_test.gno | 162 +++++++ 13 files changed, 2513 insertions(+) create mode 100644 packages/r/karma1337/geo-resto/README.md create mode 100644 packages/r/karma1337/geo-resto/auth.gno create mode 100644 packages/r/karma1337/geo-resto/auth_test.gno create mode 100644 packages/r/karma1337/geo-resto/event.gno create mode 100644 packages/r/karma1337/geo-resto/event_test.gno create mode 100644 packages/r/karma1337/geo-resto/geo_resto.gno create mode 100644 packages/r/karma1337/geo-resto/gnomod.toml create mode 100644 packages/r/karma1337/geo-resto/location.gno create mode 100644 packages/r/karma1337/geo-resto/location_test.gno create mode 100644 packages/r/karma1337/geo-resto/qr_verification_test.gno create mode 100644 packages/r/karma1337/geo-resto/renderer.gno create mode 100644 packages/r/karma1337/geo-resto/visit.gno create mode 100644 packages/r/karma1337/geo-resto/visit_test.gno diff --git a/packages/r/karma1337/geo-resto/README.md b/packages/r/karma1337/geo-resto/README.md new file mode 100644 index 0000000..60a5ed5 --- /dev/null +++ b/packages/r/karma1337/geo-resto/README.md @@ -0,0 +1,66 @@ +# 🌍 Geo-Resto + +A Gno-based realm for creating a decentralized, on-chain record of real-world places and visits. + +## Core Features + +- **Location Management**: Add and verify geographic locations, creating a permanent, user-owned registry. +- **Proof of Presence**: Check-in at locations using a secure challenge-response mechanism to prove you were there. +- **Event System**: Organize location-based events like meetups, airdrops, or community gatherings. +- **QR Code Verification**: A robust system for event organizers to securely verify attendee presence in real-time. +- **On-Chain History**: All data is immutable, timestamped, and verifiable on the Gno blockchain. + +## How It Works + +The realm is organized into modules that handle specific tasks: + +- `geo_resto.gno`: The main entry point and public API. +- `location.gno`: Manages location data. +- `visit.gno`: Handles check-ins and visit history. +- `event.gno`: Manages the event lifecycle. +- `auth.gno`: Secures the system with rate-limiting, access control, and verification logic. +- `renderer.gno`: Renders the web interface. + +## Quick Start + +### Add a Location +```gno +AddLocation("Eiffel Tower", "Iconic tower in Paris", 48.8584, 2.2945, "landmark") +``` + +### Check-In at a Location +1. **Get the challenge:** + ```gno + GetLocationChallenge("loc_1") + ``` +2. **Submit the proof (challenge response):** + ```gno + CheckIn("loc_1", "proof_from_challenge") + ``` + +### Create an Event +```gno +CreateEvent("loc_1", "Tech Meetup", "Weekly discussion", "", 1, startTime, endTime) +``` + +## Event QR Code Verification + +A secure system for proving event attendance. + +- **Organizers**: Create an event to get a QR code. At the event, generate temporary verification codes for attendees using `GenerateAttendeeCode("event_1")`. +- **Attendees**: Get verified by the organizer. +- **Stats**: Organizers can track attendance with `GetVerifiedAttendees("event_1")` and `GetEventVerificationStats("event_1")`. + +## Future Ideas + +- **Zero-Knowledge Proofs**: For privacy-preserving check-ins. +- **Community Governance**: Location ratings, reviews, and decentralized moderation. +- **Enhanced Visuals**: Richer map interfaces and data visualizations. + +## Testing + +Run the full test suite with: +```bash +gno test +``` + diff --git a/packages/r/karma1337/geo-resto/auth.gno b/packages/r/karma1337/geo-resto/auth.gno new file mode 100644 index 0000000..3e4e4ba --- /dev/null +++ b/packages/r/karma1337/geo-resto/auth.gno @@ -0,0 +1,268 @@ +package georesto + +import ( + "std" + "crypto/sha256" + "encoding/hex" + "strconv" + "time" + "math" +) + +// AuthManager handles access control and proof verification +type AuthManager struct { + trustedVerifiers map[string]bool // Trusted verifier addresses + adminAddresses map[string]bool // Admin addresses + rateLimitTracker map[string]int64 // Tracks last action time for a user and action + accessTokens map[string]int64 // token -> expiration time +} + +// NewAuthManager creates a new authentication manager instance +func NewAuthManager() *AuthManager { + return &AuthManager{ + trustedVerifiers: make(map[string]bool), + adminAddresses: make(map[string]bool), + rateLimitTracker: make(map[string]int64), + accessTokens: make(map[string]int64), + } +} + +// AddTrustedVerifier adds a trusted verifier address +func (am *AuthManager) AddTrustedVerifier(verifierAddress string, admin std.Address) bool { + if !am.IsAdmin(admin.String()) { + return false + } + + am.trustedVerifiers[verifierAddress] = true + return true +} + +// RemoveTrustedVerifier removes a trusted verifier address +func (am *AuthManager) RemoveTrustedVerifier(verifierAddress string, admin std.Address) bool { + if !am.IsAdmin(admin.String()) { + return false + } + + delete(am.trustedVerifiers, verifierAddress) + return true +} + +// IsTrustedVerifier checks if an address is a trusted verifier +func (am *AuthManager) IsTrustedVerifier(address string) bool { + return am.trustedVerifiers[address] +} + +// AddAdmin adds an admin address +func (am *AuthManager) AddAdmin(adminAddress string, currentAdmin std.Address) bool { + // Only existing admins can add new admins + if len(am.adminAddresses) > 0 && !am.IsAdmin(currentAdmin.String()) { + return false + } + + am.adminAddresses[adminAddress] = true + return true +} + +// IsAdmin checks if an address is an admin +func (am *AuthManager) IsAdmin(address string) bool { + return am.adminAddresses[address] +} + +// VerifyLocationProof verifies a cryptographic proof for location check-in +func (am *AuthManager) VerifyLocationProof(userAddress, locationID, proof string, timestamp int64) bool { + // In a real implementation, this would involve more complex verification. + // For now, we'll check if the proof is a valid challenge response. + return am.VerifyLocationChallenge(locationID, proof) +} + +// GenerateLocationProof generates a proof for location check-in +func (am *AuthManager) GenerateLocationProof(userAddress, locationID string, timestamp int64) string { + return am.generateLocationProof(userAddress, locationID, timestamp) +} + +// VerifyEventAccess verifies access to a password-protected event +func (am *AuthManager) VerifyEventAccess(eventID, password string, user std.Address) bool { + event := eventManager.GetEvent(eventID) + if event == nil { + return false + } + + // If no password required, access is granted + if event.Password == "" { + return true + } + + // Verify password + hashedPassword := am.hashString(password) + return hashedPassword == event.Password +} + +// CanModifyLocation checks if a user can modify a location +func (am *AuthManager) CanModifyLocation(locationID string, user std.Address) bool { + location := locationManager.GetLocation(locationID) + if location == nil { + return false + } + + userAddr := user.String() + + // Location creator can always modify + if location.Creator.String() == userAddr { + return true + } + + // Admins can modify any location + if am.IsAdmin(userAddr) { + return true + } + + // Trusted verifiers can modify for verification purposes + if am.IsTrustedVerifier(userAddr) { + return true + } + + return false +} + +// CanModifyEvent checks if a user can modify an event +func (am *AuthManager) CanModifyEvent(eventID string, user std.Address) bool { + event := eventManager.GetEvent(eventID) + if event == nil { + return false + } + + userAddr := user.String() + + // Event creator can always modify + if event.Creator.String() == userAddr { + return true + } + + // Admins can modify any event + if am.IsAdmin(userAddr) { + return true + } + + return false +} + +// ValidateCoordinates validates GPS coordinates +func (am *AuthManager) ValidateCoordinates(latitude, longitude float64) bool { + return latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180 +} + +// ValidateProximity checks if user is within acceptable range of location +func (am *AuthManager) ValidateProximity(userLat, userLon, locationLat, locationLon, maxDistanceKm float64) bool { + distance := haversine(userLat, userLon, locationLat, locationLon) + return distance <= maxDistanceKm +} + +// GenerateAccessToken generates a temporary access token for API access +func (am *AuthManager) GenerateAccessToken(userAddress string, expirationTime int64) string { + data := userAddress + ":" + strconv.FormatInt(expirationTime, 10) + hash := sha256.Sum256([]byte(data)) + token := hex.EncodeToString(hash[:]) + am.accessTokens[token] = expirationTime + return token +} + +// VerifyAccessToken verifies an access token +func (am *AuthManager) VerifyAccessToken(token, userAddress string, currentTime int64) bool { + expirationTime, exists := am.accessTokens[token] + if !exists { + return false // Token does not exist + } + + if currentTime > expirationTime { + delete(am.accessTokens, token) // Clean up expired token + return false // Token has expired + } + + // Optional: Verify that the token was generated for the correct userAddress. + // This would require storing the userAddress with the token or regenerating + // the token to check for a match. For now, we'll keep it simple. + + return true +} + +// CheckRateLimit implements basic rate limiting for API calls +func (am *AuthManager) CheckRateLimit(userAddress string, action string, cooldownSeconds int64) bool { + key := userAddress + ":" + action + lastActionTime, exists := am.rateLimitTracker[key] + + currentTime := time.Now().Unix() + + if exists && (currentTime-lastActionTime) < cooldownSeconds { + return false // Rate limit exceeded + } + + am.rateLimitTracker[key] = currentTime + return true +} + +// Helper methods + +func (am *AuthManager) generateLocationProof(userAddress, locationID string, timestamp int64) string { + data := userAddress + ":" + locationID + ":" + strconv.FormatInt(timestamp, 10) + hash := sha256.Sum256([]byte(data)) + return hex.EncodeToString(hash[:]) +} + +func (am *AuthManager) hashString(input string) string { + hash := sha256.Sum256([]byte(input)) + return hex.EncodeToString(hash[:]) +} + +// VerifyZKProof verifies a zero-knowledge proof (placeholder for future implementation) +func (am *AuthManager) VerifyZKProof(proof string, publicInputs []string) bool { + // Placeholder for ZK proof verification + // In a real implementation, this would verify a zk-SNARK or zk-STARK proof + // allowing users to prove they were at a location without revealing exact coordinates + return len(proof) > 0 && len(publicInputs) > 0 +} + +// GenerateLocationChallenge generates a challenge for location verification +func (am *AuthManager) GenerateLocationChallenge(locationID string) string { + // Generate a time-based challenge that can be solved by someone physically present + timestamp := time.Now().Unix() + data := locationID + ":" + strconv.FormatInt(timestamp/300, 10) // 5-minute windows + hash := sha256.Sum256([]byte(data)) + return hex.EncodeToString(hash[:8]) // Return first 8 bytes as challenge +} + +// VerifyLocationChallenge verifies a location challenge response +func (am *AuthManager) VerifyLocationChallenge(locationID, response string) bool { + // Check if response matches any of the last few time windows (for clock drift tolerance) + currentTime := time.Now().Unix() + + for i := 0; i < 3; i++ { // Check current and previous 2 windows + timeWindow := (currentTime / 300) - int64(i) + data := locationID + ":" + strconv.FormatInt(timeWindow, 10) + hash := sha256.Sum256([]byte(data)) + expectedResponse := hex.EncodeToString(hash[:8]) + + if response == expectedResponse { + return true + } + } + + return false +} + +// haversine calculates the distance between two points on Earth. +func haversine(lat1, lon1, lat2, lon2 float64) float64 { + const R = 6371 // Earth radius in kilometers + + dLat := (lat2 - lat1) * (math.Pi / 180.0) + dLon := (lon2 - lon1) * (math.Pi / 180.0) + + lat1Rad := lat1 * (math.Pi / 180.0) + lat2Rad := lat2 * (math.Pi / 180.0) + + a := math.Sin(dLat/2)*math.Sin(dLat/2) + + math.Cos(lat1Rad)*math.Cos(lat2Rad)* + math.Sin(dLon/2)*math.Sin(dLon/2) + c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) + + return R * c +} \ No newline at end of file diff --git a/packages/r/karma1337/geo-resto/auth_test.gno b/packages/r/karma1337/geo-resto/auth_test.gno new file mode 100644 index 0000000..2a777b5 --- /dev/null +++ b/packages/r/karma1337/geo-resto/auth_test.gno @@ -0,0 +1,110 @@ +package georesto + +import ( + "std" + "testing" + "time" // time.Sleep is not available in Gno +) + +func TestRateLimiting(t *testing.T) { + auth := NewAuthManager() + user := "g1user123" + action := "test_action" + cooldown := int64(5) // 5 seconds + + // First call should succeed + if !auth.CheckRateLimit(user, action, cooldown) { + t.Error("First call should not be rate-limited") + } + + // Immediate second call should fail + if auth.CheckRateLimit(user, action, cooldown) { + t.Error("Second call should be rate-limited") + } + + // NOTE: We cannot test the cooldown expiration in the Gno test environment + // because `time.Sleep` is not available and we cannot manipulate the block timestamp. +} + +func TestAdminPermissions(t *testing.T) { + auth := NewAuthManager() + admin := std.Address("g1admin") + user := std.Address("g1user") + verifier := "g1verifier" + + // Initially, no admins + if auth.IsAdmin(admin.String()) { + t.Error("Should not be admin initially") + } + + // Add admin (first one doesn't require an existing admin) + auth.AddAdmin(admin.String(), std.Address("")) + if !auth.IsAdmin(admin.String()) { + t.Fatal("Failed to add first admin") + } + + // Non-admin cannot add verifier + if auth.AddTrustedVerifier(verifier, user) { + t.Error("Non-admin should not be able to add verifier") + } + + // Admin can add verifier + if !auth.AddTrustedVerifier(verifier, admin) { + t.Error("Admin should be able to add verifier") + } + if !auth.IsTrustedVerifier(verifier) { + t.Error("Failed to verify trusted verifier") + } +} + +func TestAccessToken(t *testing.T) { + auth := NewAuthManager() + user := "g1user123" + currentTime := time.Now().Unix() + expirationTime := currentTime + 3600 // 1 hour + + // Generate a token + token := auth.GenerateAccessToken(user, expirationTime) + if len(token) != 64 { + t.Fatal("Generated token has incorrect length") + } + + // Verify the valid token + if !auth.VerifyAccessToken(token, user, currentTime) { + t.Error("Valid token should be verified") + } + + // Verify an invalid token + if auth.VerifyAccessToken("invalidtoken", user, currentTime) { + t.Error("Invalid token should not be verified") + } + + // Verify an expired token + expiredTime := currentTime + 3601 + if auth.VerifyAccessToken(token, user, expiredTime) { + t.Error("Expired token should not be verified") + } +} + +func TestLocationProof(t *testing.T) { + auth := NewAuthManager() + user := "g1user123" + locationID := "loc_1" + timestamp := time.Now().Unix() + + // Generate a valid challenge + challenge := auth.GenerateLocationChallenge(locationID) + + // The proof is the challenge itself in this implementation + proof := challenge + + // Verify the valid proof + if !auth.VerifyLocationProof(user, locationID, proof, timestamp) { + t.Error("Valid location proof should be verified") + } + + // Verify an invalid proof + if auth.VerifyLocationProof(user, locationID, "invalidproof", timestamp) { + t.Error("Invalid location proof should not be verified") + } +} diff --git a/packages/r/karma1337/geo-resto/event.gno b/packages/r/karma1337/geo-resto/event.gno new file mode 100644 index 0000000..bfae27a --- /dev/null +++ b/packages/r/karma1337/geo-resto/event.gno @@ -0,0 +1,448 @@ +package georesto + +import ( + "std" + "crypto/sha256" + "encoding/hex" + "strconv" + "strings" + "time" +) + +// EventType defines the type of location-based event +type EventType int + +const ( + EventTypeAirdrop EventType = iota + EventTypeMeetup + EventTypeEmergency + EventTypeContest + EventTypeCheckpoint +) + +// Event represents a location-based event (airdrops, meetups, etc.) +type Event struct { + ID string // Unique event identifier + LocationID string // Associated location ID + Creator std.Address // Event creator + Name string // Event name + Description string // Event description + EventType EventType // Type of event + Password string // Access password (hashed) + StartTime int64 // Event start timestamp + EndTime int64 // Event end timestamp + CreatedAt int64 // Creation timestamp + Participants []string // List of participant addresses + MaxParticipants int // Maximum number of participants (0 = unlimited) + IsActive bool // Whether the event is currently active + Rewards string // Description of rewards/airdrops + QRCode string // QR code data for event verification + VerificationSecret string // Secret key for generating verification codes + VerifiedAttendees []string // List of verified attendee addresses +} + +// EventManager handles all event-related operations +type EventManager struct { + events map[string]*Event // Event ID -> Event + eventsByLocation map[string][]string // Location ID -> Event IDs + eventsByCreator map[string][]string // Creator address -> Event IDs + activeEvents []string // Currently active event IDs + nextID int +} + +// NewEventManager creates a new event manager instance +func NewEventManager() *EventManager { + return &EventManager{ + events: make(map[string]*Event), + eventsByLocation: make(map[string][]string), + eventsByCreator: make(map[string][]string), + activeEvents: make([]string, 0), + nextID: 1, + } +} + +// CreateEvent creates a new location-based event +func (em *EventManager) CreateEvent(creator std.Address, locationID, name, description, password string, eventType int, startTime, endTime int64) *Event { + // Verify the location exists + location := locationManager.GetLocation(locationID) + if location == nil { + panic("Location does not exist") + } + + // Validate event times + currentTime := time.Now().Unix() + if startTime < currentTime { + panic("Event start time cannot be in the past") + } + if endTime <= startTime { + panic("Event end time must be after start time") + } + + // Validate required fields + if strings.TrimSpace(name) == "" { + panic("Event name is required") + } + + // Generate unique event ID + eventID := "event_" + strconv.Itoa(em.nextID) + em.nextID++ + + // Hash the password for security + hashedPassword := "" + if password != "" { + hashedPassword = em.hashPassword(password) + } + + // Generate verification secret for QR codes + verificationSecret := em.generateVerificationSecret(eventID, creator.String()) + + // Generate QR code data (contains event ID and verification info) + qrData := em.generateQRCode(eventID, verificationSecret) + + event := &Event{ + ID: eventID, + LocationID: locationID, + Creator: creator, + Name: strings.TrimSpace(name), + Description: strings.TrimSpace(description), + EventType: EventType(eventType), + Password: hashedPassword, + StartTime: startTime, + EndTime: endTime, + CreatedAt: currentTime, + Participants: make([]string, 0), + MaxParticipants: 0, // Unlimited by default + IsActive: false, + Rewards: "", + QRCode: qrData, + VerificationSecret: verificationSecret, + VerifiedAttendees: make([]string, 0), + } + + // Store the event + em.events[eventID] = event + + // Index by location + em.eventsByLocation[locationID] = append(em.eventsByLocation[locationID], eventID) + + // Index by creator + creatorAddr := creator.String() + em.eventsByCreator[creatorAddr] = append(em.eventsByCreator[creatorAddr], eventID) + + // Activate event if it should start now + em.updateEventStatus(event) + + return event +} + +// JoinEvent allows a user to join an event with proper authentication +func (em *EventManager) JoinEvent(user std.Address, eventID, password string) bool { + event := em.GetEvent(eventID) + if event == nil { + return false + } + + // Check if event is active + em.updateEventStatus(event) + if !event.IsActive { + return false + } + + // Verify password if required + if event.Password != "" { + if !em.verifyPassword(password, event.Password) { + return false + } + } + + userAddr := user.String() + + // Check if user is already a participant + for _, participant := range event.Participants { + if participant == userAddr { + return true // Already joined + } + } + + // Check participant limit + if event.MaxParticipants > 0 && len(event.Participants) >= event.MaxParticipants { + return false + } + + // Add user to participants + event.Participants = append(event.Participants, userAddr) + return true +} + +// GetEvent retrieves an event by ID +func (em *EventManager) GetEvent(eventID string) *Event { + event, exists := em.events[eventID] + if !exists { + return nil + } + return event +} + +// GetActiveEvents returns all currently active events +func (em *EventManager) GetActiveEvents() []*Event { + em.updateActiveEvents() + + events := make([]*Event, 0, len(em.activeEvents)) + for _, eventID := range em.activeEvents { + if event := em.GetEvent(eventID); event != nil { + events = append(events, event) + } + } + return events +} + +// GetLocationEvents returns all events for a specific location +func (em *EventManager) GetLocationEvents(locationID string) []*Event { + eventIDs, exists := em.eventsByLocation[locationID] + if !exists { + return []*Event{} + } + + events := make([]*Event, 0, len(eventIDs)) + for _, id := range eventIDs { + if event := em.GetEvent(id); event != nil { + em.updateEventStatus(event) + events = append(events, event) + } + } + return events +} + +// GetUserEvents returns all events created by a specific user +func (em *EventManager) GetUserEvents(userAddress string) []*Event { + eventIDs, exists := em.eventsByCreator[userAddress] + if !exists { + return []*Event{} + } + + events := make([]*Event, 0, len(eventIDs)) + for _, id := range eventIDs { + if event := em.GetEvent(id); event != nil { + em.updateEventStatus(event) + events = append(events, event) + } + } + return events +} + +// SetEventRewards sets the rewards description for an event +func (em *EventManager) SetEventRewards(eventID, rewards string, user std.Address) bool { + event := em.GetEvent(eventID) + if event == nil || event.Creator.String() != user.String() { + return false + } + + event.Rewards = rewards + return true +} + +// SetMaxParticipants sets the maximum number of participants for an event +func (em *EventManager) SetMaxParticipants(eventID string, maxParticipants int, user std.Address) bool { + event := em.GetEvent(eventID) + if event == nil || event.Creator.String() != user.String() { + return false + } + + event.MaxParticipants = maxParticipants + return true +} + +// IsUserParticipant checks if a user is participating in an event +func (em *EventManager) IsUserParticipant(eventID, userAddress string) bool { + event := em.GetEvent(eventID) + if event == nil { + return false + } + + for _, participant := range event.Participants { + if participant == userAddress { + return true + } + } + return false +} + +// updateEventStatus updates the active status of an event based on current time +func (em *EventManager) updateEventStatus(event *Event) { + currentTime := time.Now().Unix() + wasActive := event.IsActive + + event.IsActive = currentTime >= event.StartTime && currentTime <= event.EndTime + + // Update active events list if status changed + if wasActive != event.IsActive { + em.updateActiveEvents() + } +} + +// updateActiveEvents refreshes the list of active events +func (em *EventManager) updateActiveEvents() { + currentTime := time.Now().Unix() + em.activeEvents = make([]string, 0) + + for eventID, event := range em.events { + if currentTime >= event.StartTime && currentTime <= event.EndTime { + event.IsActive = true + em.activeEvents = append(em.activeEvents, eventID) + } else { + event.IsActive = false + } + } +} + +// hashPassword creates a hash of the password for secure storage +func (em *EventManager) hashPassword(password string) string { + hash := sha256.Sum256([]byte(password)) + return hex.EncodeToString(hash[:]) +} + +// verifyPassword verifies a password against its hash +func (em *EventManager) verifyPassword(password, hashedPassword string) bool { + return em.hashPassword(password) == hashedPassword +} + +// GetEventTypeString returns a string representation of the event type +func (em *EventManager) GetEventTypeString(eventType EventType) string { + switch eventType { + case EventTypeAirdrop: + return "Airdrop" + case EventTypeMeetup: + return "Meetup" + case EventTypeEmergency: + return "Emergency" + case EventTypeContest: + return "Contest" + case EventTypeCheckpoint: + return "Checkpoint" + default: + return "Unknown" + } +} + +// generateVerificationSecret creates a unique secret for event verification +func (em *EventManager) generateVerificationSecret(eventID, creatorAddress string) string { + // Create a unique secret using event ID, creator, and current time + data := eventID + ":" + creatorAddress + ":" + strconv.Itoa(int(time.Now().Unix())) + hash := sha256.Sum256([]byte(data)) + return hex.EncodeToString(hash[:])[:16] // Use first 16 chars as secret +} + +// generateQRCode creates QR code data for event verification +func (em *EventManager) generateQRCode(eventID, verificationSecret string) string { + // QR code contains: eventID|secret|timestamp + timestamp := strconv.Itoa(int(time.Now().Unix())) + qrData := eventID + "|" + verificationSecret + "|" + timestamp + return qrData +} + +// GenerateAttendeeVerificationCode creates a verification code for an attendee +func (em *EventManager) GenerateAttendeeVerificationCode(eventID string, attendeeAddress std.Address) string { + event := em.events[eventID] + if event == nil { + return "" + } + + // Only the event creator can generate verification codes + if event.Creator.String() != attendeeAddress.String() { + return "" + } + + // Generate verification code using event secret and current timestamp + timestamp := strconv.Itoa(int(time.Now().Unix())) + data := eventID + ":" + event.VerificationSecret + ":" + timestamp + hash := sha256.Sum256([]byte(data)) + return hex.EncodeToString(hash[:])[:12] // 12-character verification code +} + +// VerifyAttendeePresence verifies an attendee using their verification code +func (em *EventManager) VerifyAttendeePresence(eventID, verificationCode string, organizerAddress std.Address, attendeeAddress string) bool { + event := em.events[eventID] + if event == nil { + return false + } + + // Only the event creator/organizer can verify attendees + if event.Creator.String() != organizerAddress.String() { + return false + } + + // Check if the verification code is valid (generated within last 5 minutes) + currentTime := time.Now().Unix() + for i := 0; i < 5; i++ { // Check last 5 minutes + checkTime := currentTime - int64(i*60) + expectedData := eventID + ":" + event.VerificationSecret + ":" + strconv.Itoa(int(checkTime)) + hash := sha256.Sum256([]byte(expectedData)) + expectedCode := hex.EncodeToString(hash[:])[:12] + + if verificationCode == expectedCode { + // Add to verified attendees if not already verified + for _, verified := range event.VerifiedAttendees { + if verified == attendeeAddress { + return true // Already verified + } + } + event.VerifiedAttendees = append(event.VerifiedAttendees, attendeeAddress) + return true + } + } + + return false +} + +// GetEventQRCode returns the QR code for an event (only for event creator) +func (em *EventManager) GetEventQRCode(eventID string, requesterAddress std.Address) string { + event := em.events[eventID] + if event == nil { + return "" + } + + // Only the event creator can access the QR code + if event.Creator.String() != requesterAddress.String() { + return "" + } + + return event.QRCode +} + +// GetVerifiedAttendees returns the list of verified attendees for an event +func (em *EventManager) GetVerifiedAttendees(eventID string, requesterAddress std.Address) []string { + event := em.events[eventID] + if event == nil { + return []string{} + } + + // Only the event creator can see verified attendees + if event.Creator.String() != requesterAddress.String() { + return []string{} + } + + return event.VerifiedAttendees +} + +// GetAttendeeVerificationStats returns verification statistics for an event +func (em *EventManager) GetAttendeeVerificationStats(eventID string, requesterAddress std.Address) (int, int, float64) { + event := em.events[eventID] + if event == nil { + return 0, 0, 0.0 + } + + // Only the event creator can see stats + if event.Creator.String() != requesterAddress.String() { + return 0, 0, 0.0 + } + + totalParticipants := len(event.Participants) + verifiedAttendees := len(event.VerifiedAttendees) + verificationRate := 0.0 + + if totalParticipants > 0 { + verificationRate = float64(verifiedAttendees) / float64(totalParticipants) * 100.0 + } + + return totalParticipants, verifiedAttendees, verificationRate +} \ No newline at end of file diff --git a/packages/r/karma1337/geo-resto/event_test.gno b/packages/r/karma1337/geo-resto/event_test.gno new file mode 100644 index 0000000..28b5dd2 --- /dev/null +++ b/packages/r/karma1337/geo-resto/event_test.gno @@ -0,0 +1,211 @@ +package georesto + +import ( + "std" + "testing" + "time" +) + +func TestEventManager(t *testing.T) { + // Setup - use global managers + creator := std.Address("g1creator123") + + // Create a test location using global manager + location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") + + // Test creating an event + startTime := time.Now().Unix() + 100 // Start in 100 seconds + endTime := startTime + 3600 // End in 1 hour + + event := eventManager.CreateEvent( + creator, + location.ID, + "Test Event", + "A test event", + "password123", + int(EventTypeAirdrop), + startTime, + endTime, + ) + + if event == nil { + t.Fatal("Failed to create event") + } + + if event.Name != "Test Event" { + t.Errorf("Expected event name 'Test Event', got '%s'", event.Name) + } + + if event.LocationID != location.ID { + t.Errorf("Expected location ID %s, got %s", location.ID, event.LocationID) + } + + // Test retrieving event + retrieved := eventManager.GetEvent(event.ID) + if retrieved == nil { + t.Fatal("Failed to retrieve event") + } +} + +func TestEventJoining(t *testing.T) { + creator := std.Address("g1creator123") + location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") + + // Create an active event (starting now) + startTime := time.Now().Unix() // Starts right now + endTime := startTime + 3600 // Ends in ~1 hour + + event := eventManager.CreateEvent( + creator, + location.ID, + "Active Event", + "An active event", + "password123", + int(EventTypeMeetup), + startTime, + endTime, + ) + + user := std.Address("g1user123") + + // Test joining with correct password + success := eventManager.JoinEvent(user, event.ID, "password123") + if !success { + t.Error("Should be able to join event with correct password") + } + + // Test joining with incorrect password + success2 := eventManager.JoinEvent(user, event.ID, "wrongpassword") + if success2 { + t.Error("Should not be able to join event with incorrect password") + } + + // Test checking if user is participant + isParticipant := eventManager.IsUserParticipant(event.ID, user.String()) + if !isParticipant { + t.Error("User should be a participant") + } +} + +func TestEventStatusUpdates(t *testing.T) { + creator := std.Address("g1creator123") + location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") + + // Create a future event + currentTime := time.Now().Unix() + startTime := currentTime + 100 + endTime := startTime + 200 + + event := eventManager.CreateEvent( + creator, + location.ID, + "Future Event", + "A future event", + "", + int(EventTypeMeetup), + startTime, + endTime, + ) + + // Event should not be active yet + if event.IsActive { + t.Error("Future event should not be active") + } + + // Test active events + activeEvents := eventManager.GetActiveEvents() + eventFound := false + for _, activeEvent := range activeEvents { + if activeEvent.ID == event.ID { + eventFound = true + break + } + } + if eventFound { + t.Error("Future event should not be in active events list") + } +} + +func TestEventsByLocation(t *testing.T) { + + creator := std.Address("g1creator123") + location1 := locationManager.AddLocation(creator, "Location 1", "Test", 48.8566, 2.3522, "test") + location2 := locationManager.AddLocation(creator, "Location 2", "Test", 48.8600, 2.3500, "test") + + startTime := time.Now().Unix() + 100 + endTime := startTime + 3600 + + // Create events for different locations + event1 := eventManager.CreateEvent(creator, location1.ID, "Event 1", "Test", "", int(EventTypeAirdrop), startTime, endTime) + event2 := eventManager.CreateEvent(creator, location1.ID, "Event 2", "Test", "", int(EventTypeMeetup), startTime, endTime) + _ = eventManager.CreateEvent(creator, location2.ID, "Event 3", "Test", "", int(EventTypeContest), startTime, endTime) + + // Test getting events by location + location1Events := eventManager.GetLocationEvents(location1.ID) + if len(location1Events) != 2 { + t.Errorf("Expected 2 events for location 1, got %d", len(location1Events)) + } + + location2Events := eventManager.GetLocationEvents(location2.ID) + if len(location2Events) != 1 { + t.Errorf("Expected 1 event for location 2, got %d", len(location2Events)) + } + + // Verify event IDs + eventIDs := make(map[string]bool) + for _, event := range location1Events { + eventIDs[event.ID] = true + } + + if !eventIDs[event1.ID] || !eventIDs[event2.ID] { + t.Error("Location 1 should contain both event 1 and event 2") + } +} + +func TestEventTypeString(t *testing.T) { + + tests := []struct { + eventType EventType + expected string + }{ + {EventTypeAirdrop, "Airdrop"}, + {EventTypeMeetup, "Meetup"}, + {EventTypeEmergency, "Emergency"}, + {EventTypeContest, "Contest"}, + {EventTypeCheckpoint, "Checkpoint"}, + } + + for _, test := range tests { + result := eventManager.GetEventTypeString(test.eventType) + if result != test.expected { + t.Errorf("Expected event type string '%s', got '%s'", test.expected, result) + } + } +} + +func TestEventValidation(t *testing.T) { + + creator := std.Address("g1creator123") + location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") + + currentTime := time.Now().Unix() + + // Test creating event with invalid times + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for past start time") + } + }() + + // This should panic + eventManager.CreateEvent( + creator, + location.ID, + "Invalid Event", + "Past start time", + "", + int(EventTypeAirdrop), + currentTime-100, // Past start time + currentTime+100, + ) +} \ No newline at end of file diff --git a/packages/r/karma1337/geo-resto/geo_resto.gno b/packages/r/karma1337/geo-resto/geo_resto.gno new file mode 100644 index 0000000..86f6d4a --- /dev/null +++ b/packages/r/karma1337/geo-resto/geo_resto.gno @@ -0,0 +1,162 @@ +package georesto + +import ( + "std" + "strconv" + "strings" +) + +var ( + // locationManager handles all location-related operations + locationManager = NewLocationManager() + // visitManager manages user visits and check-ins + visitManager = NewVisitManager() + // eventManager handles location-based events and airdrops + eventManager = NewEventManager() + // renderer handles the display and formatting of geographic data + renderer = NewRenderer() + // authManager handles access control and proof verification + authManager = NewAuthManager() +) + +// Render displays the main interface for the geo-resto application +func Render(path string) string { + parts := strings.Split(path, "/") + if len(parts) == 0 { + return renderer.RenderMainPage() + } + + switch parts[0] { + case "locations": + if len(parts) > 1 { + return renderer.RenderLocation(parts[1]) + } + return renderer.RenderAllLocations() + case "visits": + if len(parts) > 1 { + return renderer.RenderUserVisits(parts[1]) + } + return renderer.RenderRecentVisits() + case "events": + if len(parts) > 1 { + return renderer.RenderEvent(parts[1]) + } + return renderer.RenderActiveEvents() + case "map": + return renderer.RenderWorldMap() + default: + return renderer.RenderMainPage() + } +} + +// Public API Functions + +// AddLocation allows adding a new geographic location to the system +func AddLocation(name, description string, latitude, longitude float64, category string) string { + caller := std.PreviousRealm().Address() + if !authManager.CheckRateLimit(caller.String(), "add_location", 60) { + return "Rate limit exceeded. Please try again later." + } + location := locationManager.AddLocation(caller, name, description, latitude, longitude, category) + return "Location added with ID: " + location.ID +} + +// CheckIn allows a user to check in at a specific location +func CheckIn(locationID, proof string) string { + caller := std.PreviousRealm().Address() + if !authManager.CheckRateLimit(caller.String(), "check_in", 60) { + return "Rate limit exceeded. Please try again later." + } + visit := visitManager.CheckIn(caller, locationID, proof) + if visit == nil { + return "Check-in failed" + } + return "Successfully checked in at location: " + locationID +} + +// CreateEvent creates a location-based event (like airdrops) +func CreateEvent(locationID, name, description, password string, eventType int, startTime, endTime int64) string { + caller := std.PreviousRealm().Address() + if !authManager.CheckRateLimit(caller.String(), "create_event", 300) { // 5 minute cooldown + return "Rate limit exceeded. Please try again later." + } + event := eventManager.CreateEvent(caller, locationID, name, description, password, eventType, startTime, endTime) + return "Event created with ID: " + event.ID +} + +// JoinEvent allows a user to join an event with proper authentication +func JoinEvent(eventID, password string) string { + caller := std.PreviousRealm().Address() + if !authManager.CheckRateLimit(caller.String(), "join_event:"+eventID, 60) { + return "Rate limit exceeded. Please try again later." + } + success := eventManager.JoinEvent(caller, eventID, password) + if success { + return "Successfully joined event: " + eventID + } + return "Failed to join event" +} + +// GetUserStats returns statistics for a specific user +func GetUserStats(userAddress string) string { + if userAddress == "" { + userAddress = std.PreviousRealm().Address().String() + } + + visitCount := visitManager.GetUserVisitCount(userAddress) + locationCount := locationManager.GetUserLocationCount(userAddress) + + return "User " + userAddress + " - Visits: " + strconv.Itoa(visitCount) + ", Locations Added: " + strconv.Itoa(locationCount) +} + +// VerifyVisit provides cryptographic proof that a user visited a location +func VerifyVisit(userAddress, locationID string, timestamp int64) bool { + return visitManager.VerifyVisit(userAddress, locationID, timestamp) +} + +// GetEventQRCode returns the QR code for event organizers to verify attendees +func GetEventQRCode(eventID string) string { + caller := std.PreviousRealm().Address() + return eventManager.GetEventQRCode(eventID, caller) +} + +// GenerateAttendeeCode generates a verification code for an attendee at an event +func GenerateAttendeeCode(eventID string) string { + caller := std.PreviousRealm().Address() + return eventManager.GenerateAttendeeVerificationCode(eventID, caller) +} + +// VerifyAttendeePresence allows event organizers to verify attendee presence using verification codes +func VerifyAttendeePresence(eventID, verificationCode, attendeeAddress string) string { + caller := std.PreviousRealm().Address() + success := eventManager.VerifyAttendeePresence(eventID, verificationCode, caller, attendeeAddress) + if success { + return "Attendee verified successfully for event: " + eventID + } + return "Failed to verify attendee presence" +} + +// GetVerifiedAttendees returns the list of verified attendees for an event +func GetVerifiedAttendees(eventID string) string { + caller := std.PreviousRealm().Address() + attendees := eventManager.GetVerifiedAttendees(eventID, caller) + if len(attendees) == 0 { + return "No verified attendees for event: " + eventID + } + return "Verified attendees (" + strconv.Itoa(len(attendees)) + "): " + strings.Join(attendees, ", ") +} + +// GetEventVerificationStats returns verification statistics for an event +func GetEventVerificationStats(eventID string) string { + caller := std.PreviousRealm().Address() + total, verified, rate := eventManager.GetAttendeeVerificationStats(eventID, caller) + rateStr := strconv.Itoa(int(rate)) + "." + strconv.Itoa(int((rate-float64(int(rate)))*10)) + return "Event " + eventID + " - Total Participants: " + strconv.Itoa(total) + + ", Verified: " + strconv.Itoa(verified) + + ", Verification Rate: " + rateStr + "%" +} + +// GetLocationChallenge returns a challenge for a specific location +func GetLocationChallenge(locationID string) string { + return authManager.GenerateLocationChallenge(locationID) +} diff --git a/packages/r/karma1337/geo-resto/gnomod.toml b/packages/r/karma1337/geo-resto/gnomod.toml new file mode 100644 index 0000000..10d91c8 --- /dev/null +++ b/packages/r/karma1337/geo-resto/gnomod.toml @@ -0,0 +1,2 @@ +module = "gno.land/r/karma1337/georesto" +gno = "0.9" diff --git a/packages/r/karma1337/geo-resto/location.gno b/packages/r/karma1337/geo-resto/location.gno new file mode 100644 index 0000000..b6f7025 --- /dev/null +++ b/packages/r/karma1337/geo-resto/location.gno @@ -0,0 +1,164 @@ +package georesto + +import ( + "std" + "strconv" + "strings" + "time" +) + +// Location represents a geographic point with metadata +type Location struct { + ID string // Unique identifier + Name string // Human-readable name + Description string // Detailed description + Latitude float64 // GPS latitude + Longitude float64 // GPS longitude + Category string // Type of location (restaurant, landmark, etc.) + Creator std.Address // Address of the user who added this location + CreatedAt int64 // Unix timestamp + VisitCount int // Number of check-ins at this location + Verified bool // Whether location is verified by community +} + +// LocationManager handles all location-related operations +type LocationManager struct { + locations map[string]*Location // ID -> Location + locationsByUser map[string][]string // User address -> Location IDs + nextID int +} + +// NewLocationManager creates a new location manager instance +func NewLocationManager() *LocationManager { + return &LocationManager{ + locations: make(map[string]*Location), + locationsByUser: make(map[string][]string), + nextID: 1, + } +} + +// AddLocation adds a new location to the system +func (lm *LocationManager) AddLocation(creator std.Address, name, description string, latitude, longitude float64, category string) *Location { + // Validate coordinates + if latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180 { + panic("Invalid coordinates") + } + + // Validate required fields + if strings.TrimSpace(name) == "" { + panic("Location name is required") + } + + // Generate unique ID + id := "loc_" + strconv.Itoa(lm.nextID) + lm.nextID++ + + location := &Location{ + ID: id, + Name: strings.TrimSpace(name), + Description: strings.TrimSpace(description), + Latitude: latitude, + Longitude: longitude, + Category: strings.TrimSpace(category), + Creator: creator, + CreatedAt: time.Now().Unix(), + VisitCount: 0, + Verified: false, + } + + lm.locations[id] = location + + // Track locations by user + userAddr := creator.String() + lm.locationsByUser[userAddr] = append(lm.locationsByUser[userAddr], id) + + return location +} + +// GetLocation retrieves a location by ID +func (lm *LocationManager) GetLocation(id string) *Location { + location, exists := lm.locations[id] + if !exists { + return nil + } + return location +} + +// GetAllLocations returns all locations in the system +func (lm *LocationManager) GetAllLocations() []*Location { + locations := make([]*Location, 0, len(lm.locations)) + for _, location := range lm.locations { + locations = append(locations, location) + } + return locations +} + +// GetLocationsByUser returns all locations created by a specific user +func (lm *LocationManager) GetLocationsByUser(userAddress string) []*Location { + locationIDs, exists := lm.locationsByUser[userAddress] + if !exists { + return []*Location{} + } + + locations := make([]*Location, 0, len(locationIDs)) + for _, id := range locationIDs { + if location := lm.GetLocation(id); location != nil { + locations = append(locations, location) + } + } + return locations +} + +// GetLocationsByCategory returns all locations in a specific category +func (lm *LocationManager) GetLocationsByCategory(category string) []*Location { + locations := make([]*Location, 0) + for _, location := range lm.locations { + if strings.EqualFold(location.Category, category) { + locations = append(locations, location) + } + } + return locations +} + +// IncrementVisitCount increases the visit count for a location +func (lm *LocationManager) IncrementVisitCount(locationID string) { + if location := lm.GetLocation(locationID); location != nil { + location.VisitCount++ + } +} + +// GetUserLocationCount returns the number of locations created by a user +func (lm *LocationManager) GetUserLocationCount(userAddress string) int { + locationIDs, exists := lm.locationsByUser[userAddress] + if !exists { + return 0 + } + return len(locationIDs) +} + +// VerifyLocation marks a location as verified (community consensus mechanism) +func (lm *LocationManager) VerifyLocation(locationID string, verifier std.Address) bool { + location := lm.GetLocation(locationID) + if location == nil { + return false + } + + // Simple verification - in a real system, this would involve community voting + // or trusted verifier addresses + location.Verified = true + return true +} + +// GetNearbyLocations returns locations within a given radius using the Haversine formula. +func (lm *LocationManager) GetNearbyLocations(latitude, longitude, radiusKm float64) []*Location { + nearby := make([]*Location, 0) + + for _, location := range lm.locations { + distance := haversine(latitude, longitude, location.Latitude, location.Longitude) + if distance <= radiusKm { + nearby = append(nearby, location) + } + } + + return nearby +} \ No newline at end of file diff --git a/packages/r/karma1337/geo-resto/location_test.gno b/packages/r/karma1337/geo-resto/location_test.gno new file mode 100644 index 0000000..9f19477 --- /dev/null +++ b/packages/r/karma1337/geo-resto/location_test.gno @@ -0,0 +1,142 @@ +package georesto + +import ( + "std" + "testing" +) + +func TestLocationManager(t *testing.T) { + // Test adding a location using global manager + creator := std.Address("g1test123") + location := locationManager.AddLocation(creator, "Test Restaurant", "A great place to eat", 48.8566, 2.3522, "restaurant") + + if location == nil { + t.Fatal("Failed to create location") + } + + if location.Name != "Test Restaurant" { + t.Errorf("Expected name 'Test Restaurant', got '%s'", location.Name) + } + + if location.Latitude != 48.8566 { + t.Errorf("Expected latitude 48.8566, got %f", location.Latitude) + } + + // Test retrieving the location + retrieved := locationManager.GetLocation(location.ID) + if retrieved == nil { + t.Fatal("Failed to retrieve location") + } + + if retrieved.Name != location.Name { + t.Errorf("Retrieved location name mismatch") + } + + // Test getting locations by user + userLocations := locationManager.GetLocationsByUser(creator.String()) + if len(userLocations) != 1 { + t.Errorf("Expected 1 location for user, got %d", len(userLocations)) + } + + // Test invalid coordinates + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic for invalid coordinates") + } + }() + locationManager.AddLocation(creator, "Invalid Location", "Bad coords", 91.0, 0.0, "test") +} + +func TestLocationCategories(t *testing.T) { + creator := std.Address("g1test123") + + // Add locations in different categories + locationManager.AddLocation(creator, "Restaurant 1", "Food", 48.8566, 2.3522, "restaurant") + locationManager.AddLocation(creator, "Museum 1", "Culture", 48.8606, 2.3376, "museum") + locationManager.AddLocation(creator, "Restaurant 2", "More food", 48.8584, 2.2945, "restaurant") + + // Test getting by category + restaurants := locationManager.GetLocationsByCategory("restaurant") + if len(restaurants) < 2 { + t.Errorf("Expected at least 2 restaurants, got %d", len(restaurants)) + } + + museums := locationManager.GetLocationsByCategory("museum") + if len(museums) < 1 { + t.Errorf("Expected at least 1 museum, got %d", len(museums)) + } +} + +func TestLocationVisitCount(t *testing.T) { + creator := std.Address("g1test123") + + location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") + + if location.VisitCount != 0 { + t.Errorf("Expected initial visit count 0, got %d", location.VisitCount) + } + + // Increment visit count + locationManager.IncrementVisitCount(location.ID) + + retrieved := locationManager.GetLocation(location.ID) + if retrieved.VisitCount != 1 { + t.Errorf("Expected visit count 1, got %d", retrieved.VisitCount) + } +} + +func TestLocationVerification(t *testing.T) { + creator := std.Address("g1test123") + verifier := std.Address("g1verifier123") + + location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") + + if location.Verified { + t.Error("Location should not be verified initially") + } + + // Verify location + success := locationManager.VerifyLocation(location.ID, verifier) + if !success { + t.Error("Failed to verify location") + } + + retrieved := locationManager.GetLocation(location.ID) + if !retrieved.Verified { + t.Error("Location should be verified") + } +} + +func TestNearbyLocations(t *testing.T) { + lm := NewLocationManager() + creator := std.Address("g1creator") + + // Paris + lm.AddLocation(creator, "Eiffel Tower", "", 48.8584, 2.2945, "landmark") + // London + lm.AddLocation(creator, "Big Ben", "", 51.5007, -0.1246, "landmark") + // New York + lm.AddLocation(creator, "Statue of Liberty", "", 40.6892, -74.0445, "landmark") + + // Search near Paris + nearby := lm.GetNearbyLocations(48.8566, 2.3522, 10) // 10 km radius + if len(nearby) != 1 { + t.Errorf("Expected 1 nearby location, got %d", len(nearby)) + } + if nearby[0].Name != "Eiffel Tower" { + t.Errorf("Expected Eiffel Tower, got %s", nearby[0].Name) + } + + // Search with a larger radius to include London + nearby = lm.GetNearbyLocations(48.8566, 2.3522, 400) // 400 km radius + var foundLondon bool + for _, loc := range nearby { + if loc.Name == "Big Ben" { + foundLondon = true + break + } + } + if !foundLondon { + t.Errorf("Expected to find London in a 400km radius of Paris") + } +} \ No newline at end of file diff --git a/packages/r/karma1337/geo-resto/qr_verification_test.gno b/packages/r/karma1337/geo-resto/qr_verification_test.gno new file mode 100644 index 0000000..6715f49 --- /dev/null +++ b/packages/r/karma1337/geo-resto/qr_verification_test.gno @@ -0,0 +1,131 @@ +package georesto + +import ( + "std" + "testing" + "time" +) + +func TestQRCodeVerification(t *testing.T) { + // Setup - use global managers to ensure they work together + creator := std.Address("g1creator123") + + // Create a location first using global manager + location := locationManager.AddLocation(creator, "Test Venue", "A test venue for events", 40.7128, -74.0060, "venue") + if location == nil { + t.Fatal("Failed to create location") + } + + // Create an event using global manager + currentTime := time.Now().Unix() + startTime := currentTime + 60 // Starts in 1 minute (close enough to current time) + endTime := currentTime + 7200 // Ends in 2 hours + + event := eventManager.CreateEvent(creator, location.ID, "QR Test Event", "Testing QR verification", "", int(EventTypeMeetup), startTime, endTime) + if event == nil { + t.Fatal("Failed to create event") + } + + // Test QR code generation + if event.QRCode == "" { + t.Error("QR code should be generated automatically") + } + + if event.VerificationSecret == "" { + t.Error("Verification secret should be generated automatically") + } + + // Test verification code generation + verificationCode := eventManager.GenerateAttendeeVerificationCode(event.ID, creator) + if verificationCode == "" { + t.Error("Should generate verification code for event creator") + } + + if len(verificationCode) != 12 { + t.Errorf("Verification code should be 12 characters, got %d", len(verificationCode)) + } + + // Test attendee verification + attendeeAddress := "g1attendee123" + success := eventManager.VerifyAttendeePresence(event.ID, verificationCode, creator, attendeeAddress) + if !success { + t.Error("Should successfully verify attendee with valid code") + } + + // Check that attendee is now in verified list + verifiedAttendees := eventManager.GetVerifiedAttendees(event.ID, creator) + if len(verifiedAttendees) != 1 { + t.Errorf("Should have 1 verified attendee, got %d", len(verifiedAttendees)) + } + + if verifiedAttendees[0] != attendeeAddress { + t.Errorf("Expected attendee %s, got %s", attendeeAddress, verifiedAttendees[0]) + } + + // Test duplicate verification (should not add twice) + success2 := eventManager.VerifyAttendeePresence(event.ID, verificationCode, creator, attendeeAddress) + if !success2 { + t.Error("Should still return true for already verified attendee") + } + + verifiedAttendees2 := eventManager.GetVerifiedAttendees(event.ID, creator) + if len(verifiedAttendees2) != 1 { + t.Error("Should not add duplicate verified attendee") + } + + // Test invalid verification code + invalidCode := "invalid12345" + success3 := eventManager.VerifyAttendeePresence(event.ID, invalidCode, creator, "g1other123") + if success3 { + t.Error("Should not verify with invalid code") + } + + // Test access control - non-creator shouldn't access QR code + nonCreator := std.Address("g1other123") + qrCode := eventManager.GetEventQRCode(event.ID, nonCreator) + if qrCode != "" { + t.Error("Non-creator should not access QR code") + } + + // Test verification statistics + total, verified, _ := eventManager.GetAttendeeVerificationStats(event.ID, creator) + if total != 0 || verified != 1 { + t.Errorf("Expected stats: total=0, verified=1, got total=%d, verified=%d", total, verified) + } + + // Add a participant manually (since event might not be active in test) + // This simulates what would happen if JoinEvent succeeded + event.Participants = append(event.Participants, attendeeAddress) + total2, verified2, rate2 := eventManager.GetAttendeeVerificationStats(event.ID, creator) + if total2 != 1 || verified2 != 1 || rate2 != 100.0 { + t.Errorf("Expected stats: total=1, verified=1, rate=100.0, got total=%d, verified=%d, rate=%f", total2, verified2, rate2) + } +} + +func TestQRCodeTimeSecurity(t *testing.T) { + // This test verifies that verification codes are time-limited + // Note: In a real test environment, we'd manipulate time, but this shows the concept + + creator := std.Address("g1creator123") + + // Create location and event using global managers + location := locationManager.AddLocation(creator, "Time Test Venue", "Testing time security", 40.7128, -74.0060, "venue") + startTime := time.Now().Unix() + 3600 + endTime := startTime + 7200 + + event := eventManager.CreateEvent(creator, location.ID, "Time Security Test", "Testing time-based security", "", int(EventTypeMeetup), startTime, endTime) + + // Generate a verification code + code1 := eventManager.GenerateAttendeeVerificationCode(event.ID, creator) + + // Wait a moment and generate another code (should be different due to timestamp) + // Note: In testing environment, time might not advance enough to see difference + code2 := eventManager.GenerateAttendeeVerificationCode(event.ID, creator) + + // Codes might be the same in testing environment due to same timestamp + // But the logic ensures they would be different in real-time usage + + if code1 == "" || code2 == "" { + t.Error("Both verification codes should be generated") + } +} \ No newline at end of file diff --git a/packages/r/karma1337/geo-resto/renderer.gno b/packages/r/karma1337/geo-resto/renderer.gno new file mode 100644 index 0000000..91789c0 --- /dev/null +++ b/packages/r/karma1337/geo-resto/renderer.gno @@ -0,0 +1,430 @@ +package georesto + +import ( + "strconv" + "strings" + "time" +) + +// Renderer handles the display and formatting of geographic data for web interface +type Renderer struct{} + +// NewRenderer creates a new renderer instance +func NewRenderer() *Renderer { + return &Renderer{} +} + +// RenderMainPage displays the main interface +func (r *Renderer) RenderMainPage() string { + var sb strings.Builder + + sb.WriteString("# 🌍 Geo-Resto - Decentralized Geographic Data\n\n") + sb.WriteString("Welcome to Geo-Resto, a blockchain-based platform for sharing and verifying geographic data.\n\n") + + // Statistics + allLocations := locationManager.GetAllLocations() + activeEvents := eventManager.GetActiveEvents() + + sb.WriteString("## πŸ“Š Platform Statistics\n") + sb.WriteString("- **Total Locations**: " + strconv.Itoa(len(allLocations)) + "\n") + sb.WriteString("- **Active Events**: " + strconv.Itoa(len(activeEvents)) + "\n") + sb.WriteString("- **Total Visits**: " + r.getTotalVisitCount() + "\n\n") + + // Navigation + sb.WriteString("## πŸ—ΊοΈ Navigation\n") + sb.WriteString("- [View All Locations](/r/karma1337/georesto:locations)\n") + sb.WriteString("- [Recent Visits](/r/karma1337/georesto:visits)\n") + sb.WriteString("- [Active Events](/r/karma1337/georesto:events)\n") + sb.WriteString("- [World Map](/r/karma1337/georesto:map)\n\n") + + // Recent activity + sb.WriteString("## πŸ”₯ Recent Activity\n") + recentVisits := visitManager.GetRecentVisits(5) + if len(recentVisits) == 0 { + sb.WriteString("No recent visits.\n") + } else { + for _, visit := range recentVisits { + location := locationManager.GetLocation(visit.LocationID) + if location != nil { + sb.WriteString("- " + r.truncateAddress(visit.UserAddress) + " visited **" + location.Name + "** " + r.timeAgo(visit.Timestamp) + "\n") + } + } + } + + return sb.String() +} + +// RenderAllLocations displays all locations in the system +func (r *Renderer) RenderAllLocations() string { + var sb strings.Builder + + sb.WriteString("# πŸ“ All Locations\n\n") + + locations := locationManager.GetAllLocations() + if len(locations) == 0 { + sb.WriteString("No locations have been added yet.\n") + return sb.String() + } + + // Group by category + categories := make(map[string][]*Location) + for _, location := range locations { + category := location.Category + if category == "" { + category = "Uncategorized" + } + categories[category] = append(categories[category], location) + } + + for category, categoryLocations := range categories { + sb.WriteString("## " + category + "\n\n") + for _, location := range categoryLocations { + sb.WriteString(r.renderLocationCard(location)) + } + sb.WriteString("\n") + } + + return sb.String() +} + +// RenderLocation displays details for a specific location +func (r *Renderer) RenderLocation(locationID string) string { + var sb strings.Builder + + location := locationManager.GetLocation(locationID) + if location == nil { + return "Location not found." + } + + sb.WriteString("# πŸ“ " + location.Name + "\n\n") + + // Location details + sb.WriteString("**Description**: " + location.Description + "\n") + sb.WriteString("**Category**: " + location.Category + "\n") + sb.WriteString("**Coordinates**: " + strconv.FormatFloat(location.Latitude, 'f', 6, 64) + ", " + strconv.FormatFloat(location.Longitude, 'f', 6, 64) + "\n") + sb.WriteString("**Created by**: " + r.truncateAddress(location.Creator.String()) + "\n") + sb.WriteString("**Created**: " + r.formatTimestamp(location.CreatedAt) + "\n") + sb.WriteString("**Total Visits**: " + strconv.Itoa(location.VisitCount) + "\n") + if location.Verified { + sb.WriteString("**Status**: βœ… Verified\n") + } else { + sb.WriteString("**Status**: ⏳ Pending verification\n") + } + sb.WriteString("\n") + + // Recent visits + visits := visitManager.GetLocationVisits(locationID) + sb.WriteString("## πŸ”„ Recent Visits (" + strconv.Itoa(len(visits)) + ")\n\n") + + if len(visits) == 0 { + sb.WriteString("No visits recorded yet.\n") + } else { + // Show last 10 visits + limit := len(visits) + if limit > 10 { + limit = 10 + } + + for i := len(visits) - limit; i < len(visits); i++ { + visit := visits[i] + sb.WriteString("- " + r.truncateAddress(visit.UserAddress) + " - " + r.formatTimestamp(visit.Timestamp)) + if visit.Verified { + sb.WriteString(" βœ…") + } + sb.WriteString("\n") + } + } + + // Associated events + events := eventManager.GetLocationEvents(locationID) + if len(events) > 0 { + sb.WriteString("\n## 🎯 Associated Events\n\n") + for _, event := range events { + sb.WriteString(r.renderEventCard(event)) + } + } + + return sb.String() +} + +// RenderUserVisits displays visits for a specific user +func (r *Renderer) RenderUserVisits(userAddress string) string { + var sb strings.Builder + + sb.WriteString("# πŸ‘€ User Visits: " + r.truncateAddress(userAddress) + "\n\n") + + visits := visitManager.GetUserVisits(userAddress) + if len(visits) == 0 { + sb.WriteString("No visits recorded for this user.\n") + return sb.String() + } + + sb.WriteString("**Total Visits**: " + strconv.Itoa(len(visits)) + "\n\n") + + for _, visit := range visits { + location := locationManager.GetLocation(visit.LocationID) + if location != nil { + sb.WriteString("## " + location.Name + "\n") + sb.WriteString("- **Visited**: " + r.formatTimestamp(visit.Timestamp) + "\n") + sb.WriteString("- **Location**: " + location.Category + " at " + strconv.FormatFloat(location.Latitude, 'f', 4, 64) + ", " + strconv.FormatFloat(location.Longitude, 'f', 4, 64) + "\n") + if visit.Verified { + sb.WriteString("- **Status**: βœ… Verified\n") + } else { + sb.WriteString("- **Status**: ⏳ Pending verification\n") + } + if visit.Notes != "" { + sb.WriteString("- **Notes**: " + visit.Notes + "\n") + } + sb.WriteString("\n") + } + } + + return sb.String() +} + +// RenderRecentVisits displays recent visits across all users +func (r *Renderer) RenderRecentVisits() string { + var sb strings.Builder + + sb.WriteString("# πŸ”„ Recent Visits\n\n") + + visits := visitManager.GetRecentVisits(20) + if len(visits) == 0 { + sb.WriteString("No visits recorded yet.\n") + return sb.String() + } + + for _, visit := range visits { + location := locationManager.GetLocation(visit.LocationID) + if location != nil { + sb.WriteString("- **" + location.Name + "** visited by " + r.truncateAddress(visit.UserAddress) + " " + r.timeAgo(visit.Timestamp)) + if visit.Verified { + sb.WriteString(" βœ…") + } + sb.WriteString("\n") + } + } + + return sb.String() +} + +// RenderActiveEvents displays all active events +func (r *Renderer) RenderActiveEvents() string { + var sb strings.Builder + + sb.WriteString("# 🎯 Active Events\n\n") + + events := eventManager.GetActiveEvents() + if len(events) == 0 { + sb.WriteString("No active events at the moment.\n") + return sb.String() + } + + for _, event := range events { + sb.WriteString(r.renderEventCard(event)) + } + + return sb.String() +} + +// RenderEvent displays details for a specific event +func (r *Renderer) RenderEvent(eventID string) string { + var sb strings.Builder + + event := eventManager.GetEvent(eventID) + if event == nil { + return "Event not found." + } + + sb.WriteString("# 🎯 " + event.Name + "\n\n") + + // Event details + sb.WriteString("**Description**: " + event.Description + "\n") + sb.WriteString("**Type**: " + eventManager.GetEventTypeString(event.EventType) + "\n") + sb.WriteString("**Location**: ") + + location := locationManager.GetLocation(event.LocationID) + if location != nil { + sb.WriteString("[" + location.Name + "](/r/karma1337/geo-resto:locations/" + event.LocationID + ")") + } else { + sb.WriteString(event.LocationID) + } + sb.WriteString("\n") + + sb.WriteString("**Created by**: " + r.truncateAddress(event.Creator.String()) + "\n") + sb.WriteString("**Start Time**: " + r.formatTimestamp(event.StartTime) + "\n") + sb.WriteString("**End Time**: " + r.formatTimestamp(event.EndTime) + "\n") + sb.WriteString("**Participants**: " + strconv.Itoa(len(event.Participants))) + if event.MaxParticipants > 0 { + sb.WriteString(" / " + strconv.Itoa(event.MaxParticipants)) + } + sb.WriteString("\n") + + if event.IsActive { + sb.WriteString("**Status**: 🟒 Active\n") + } else { + sb.WriteString("**Status**: πŸ”΄ Inactive\n") + } + + if event.Rewards != "" { + sb.WriteString("**Rewards**: " + event.Rewards + "\n") + } + + if event.Password != "" { + sb.WriteString("**Access**: πŸ”’ Password required\n") + } + + sb.WriteString("\n") + + // QR Code and Verification Info (for event organizers) + sb.WriteString("## πŸ“± Event Verification\n\n") + sb.WriteString("**QR Code**: `" + event.QRCode + "`\n\n") + + // Verification Statistics + totalParticipants := len(event.Participants) + verifiedCount := len(event.VerifiedAttendees) + if totalParticipants > 0 { + verificationRate := float64(verifiedCount) / float64(totalParticipants) * 100.0 + rateStr := strconv.Itoa(int(verificationRate)) + "." + strconv.Itoa(int((verificationRate-float64(int(verificationRate)))*10)) + sb.WriteString("**Verification Stats**: " + strconv.Itoa(verifiedCount) + "/" + strconv.Itoa(totalParticipants) + " verified (" + rateStr + "%)\n\n") + } + + // Verified Attendees List + if len(event.VerifiedAttendees) > 0 { + sb.WriteString("### βœ… Verified Attendees\n\n") + for _, attendee := range event.VerifiedAttendees { + sb.WriteString("- " + r.truncateAddress(attendee) + "\n") + } + sb.WriteString("\n") + } + + sb.WriteString("\n") + + // Participants list + if len(event.Participants) > 0 { + sb.WriteString("## πŸ‘₯ Participants\n\n") + for i, participant := range event.Participants { + sb.WriteString(strconv.Itoa(i+1) + ". " + r.truncateAddress(participant) + "\n") + } + } + + return sb.String() +} + +// RenderWorldMap displays a text-based world map representation +func (r *Renderer) RenderWorldMap() string { + var sb strings.Builder + + sb.WriteString("# πŸ—ΊοΈ World Map\n\n") + sb.WriteString("*Text-based representation of location distribution*\n\n") + + locations := locationManager.GetAllLocations() + if len(locations) == 0 { + sb.WriteString("No locations to display on the map.\n") + return sb.String() + } + + // Group locations by region (simplified) + regions := make(map[string][]*Location) + for _, location := range locations { + region := r.getRegionFromCoordinates(location.Latitude, location.Longitude) + regions[region] = append(regions[region], location) + } + + sb.WriteString("## 🌍 Locations by Region\n\n") + for region, regionLocations := range regions { + sb.WriteString("### " + region + " (" + strconv.Itoa(len(regionLocations)) + " locations)\n") + for _, location := range regionLocations { + sb.WriteString("- **" + location.Name + "** (" + strconv.FormatFloat(location.Latitude, 'f', 2, 64) + ", " + strconv.FormatFloat(location.Longitude, 'f', 2, 64) + ")\n") + } + sb.WriteString("\n") + } + + return sb.String() +} + +// Helper methods + +func (r *Renderer) renderLocationCard(location *Location) string { + var sb strings.Builder + + sb.WriteString("### [" + location.Name + "](/r/karma1337/geo-resto:locations/" + location.ID + ")\n") + sb.WriteString(location.Description + "\n\n") + sb.WriteString("πŸ“ *" + strconv.FormatFloat(location.Latitude, 'f', 4, 64) + ", " + strconv.FormatFloat(location.Longitude, 'f', 4, 64) + "*") + sb.WriteString(" | πŸ‘₯ " + strconv.Itoa(location.VisitCount) + " visits") + if location.Verified { + sb.WriteString(" | βœ… Verified") + } + sb.WriteString("\n\n") + + return sb.String() +} + +func (r *Renderer) renderEventCard(event *Event) string { + var sb strings.Builder + + sb.WriteString("### [" + event.Name + "](/r/karma1337/geo-resto:events/" + event.ID + ")\n") + sb.WriteString(event.Description + "\n\n") + sb.WriteString("πŸ“… " + r.formatTimestamp(event.StartTime) + " - " + r.formatTimestamp(event.EndTime) + "\n") + sb.WriteString("πŸ‘₯ " + strconv.Itoa(len(event.Participants)) + " participants") + if event.IsActive { + sb.WriteString(" | 🟒 Active") + } + sb.WriteString("\n\n") + + return sb.String() +} + +func (r *Renderer) truncateAddress(address string) string { + if len(address) <= 12 { + return address + } + return address[:6] + "..." + address[len(address)-6:] +} + +func (r *Renderer) formatTimestamp(timestamp int64) string { + t := time.Unix(timestamp, 0) + return t.Format("2006-01-02 15:04:05") +} + +func (r *Renderer) timeAgo(timestamp int64) string { + now := time.Now().Unix() + diff := now - timestamp + + if diff < 60 { + return "just now" + } else if diff < 3600 { + return strconv.FormatInt(diff/60, 10) + " minutes ago" + } else if diff < 86400 { + return strconv.FormatInt(diff/3600, 10) + " hours ago" + } else { + return strconv.FormatInt(diff/86400, 10) + " days ago" + } +} + +func (r *Renderer) getTotalVisitCount() string { + count := 0 + for _, location := range locationManager.GetAllLocations() { + count += location.VisitCount + } + return strconv.Itoa(count) +} + +func (r *Renderer) getRegionFromCoordinates(lat, lon float64) string { + // Simplified region mapping + if lat >= 30 && lat <= 70 && lon >= -10 && lon <= 50 { + return "Europe" + } else if lat >= 25 && lat <= 50 && lon >= -130 && lon <= -60 { + return "North America" + } else if lat >= -35 && lat <= 15 && lon >= -85 && lon <= -35 { + return "South America" + } else if lat >= -35 && lat <= 35 && lon >= 10 && lon <= 55 { + return "Africa" + } else if lat >= 5 && lat <= 80 && lon >= 50 && lon <= 180 { + return "Asia" + } else if lat >= -50 && lat <= -10 && lon >= 110 && lon <= 180 { + return "Australia/Oceania" + } else { + return "Other" + } +} \ No newline at end of file diff --git a/packages/r/karma1337/geo-resto/visit.gno b/packages/r/karma1337/geo-resto/visit.gno new file mode 100644 index 0000000..d8874bb --- /dev/null +++ b/packages/r/karma1337/geo-resto/visit.gno @@ -0,0 +1,217 @@ +package georesto + +import ( + "std" + "crypto/sha256" + "encoding/hex" + "strconv" + "time" +) + +// Visit represents a user's check-in at a location +type Visit struct { + ID string // Unique visit identifier + UserAddress string // Address of the user who visited + LocationID string // ID of the visited location + Timestamp int64 // Unix timestamp of the visit + Proof string // Cryptographic proof or verification data + ProofHash string // Hash of the proof for integrity + Verified bool // Whether the visit has been verified + Notes string // Optional notes about the visit +} + +// VisitManager handles all visit and check-in related operations +type VisitManager struct { + visits map[string]*Visit // Visit ID -> Visit + visitsByUser map[string][]string // User address -> Visit IDs + visitsByLocation map[string][]string // Location ID -> Visit IDs + nextID int +} + +// NewVisitManager creates a new visit manager instance +func NewVisitManager() *VisitManager { + return &VisitManager{ + visits: make(map[string]*Visit), + visitsByUser: make(map[string][]string), + visitsByLocation: make(map[string][]string), + nextID: 1, + } +} + +// CheckIn records a user's visit to a location +func (vm *VisitManager) CheckIn(user std.Address, locationID, proof string) *Visit { + // Verify the location exists + location := locationManager.GetLocation(locationID) + if location == nil { + return nil + } + + userAddr := user.String() + currentTime := time.Now().Unix() + + // Check for duplicate recent check-ins (prevent spam) + if vm.hasRecentCheckIn(userAddr, locationID, currentTime, 3600) { // 1 hour cooldown + return nil + } + + // Generate unique visit ID + visitID := "visit_" + strconv.Itoa(vm.nextID) + vm.nextID++ + + // Create proof hash for integrity + proofHash := vm.generateProofHash(userAddr, locationID, proof, currentTime) + + visit := &Visit{ + ID: visitID, + UserAddress: userAddr, + LocationID: locationID, + Timestamp: currentTime, + Proof: proof, + ProofHash: proofHash, + Verified: vm.verifyProof(proof, userAddr, locationID), + Notes: "", + } + + // Store the visit + vm.visits[visitID] = visit + + // Index by user + vm.visitsByUser[userAddr] = append(vm.visitsByUser[userAddr], visitID) + + // Index by location + vm.visitsByLocation[locationID] = append(vm.visitsByLocation[locationID], visitID) + + // Update location visit count + locationManager.IncrementVisitCount(locationID) + + return visit +} + +// GetVisit retrieves a visit by ID +func (vm *VisitManager) GetVisit(visitID string) *Visit { + visit, exists := vm.visits[visitID] + if !exists { + return nil + } + return visit +} + +// GetUserVisits returns all visits by a specific user +func (vm *VisitManager) GetUserVisits(userAddress string) []*Visit { + visitIDs, exists := vm.visitsByUser[userAddress] + if !exists { + return []*Visit{} + } + + visits := make([]*Visit, 0, len(visitIDs)) + for _, id := range visitIDs { + if visit := vm.GetVisit(id); visit != nil { + visits = append(visits, visit) + } + } + return visits +} + +// GetLocationVisits returns all visits to a specific location +func (vm *VisitManager) GetLocationVisits(locationID string) []*Visit { + visitIDs, exists := vm.visitsByLocation[locationID] + if !exists { + return []*Visit{} + } + + visits := make([]*Visit, 0, len(visitIDs)) + for _, id := range visitIDs { + if visit := vm.GetVisit(id); visit != nil { + visits = append(visits, visit) + } + } + return visits +} + +// GetUserVisitCount returns the total number of visits by a user +func (vm *VisitManager) GetUserVisitCount(userAddress string) int { + visitIDs, exists := vm.visitsByUser[userAddress] + if !exists { + return 0 + } + return len(visitIDs) +} + +// GetRecentVisits returns the most recent visits across all users +func (vm *VisitManager) GetRecentVisits(limit int) []*Visit { + allVisits := make([]*Visit, 0, len(vm.visits)) + for _, visit := range vm.visits { + allVisits = append(allVisits, visit) + } + + // Use a more efficient sorting algorithm (insertion sort) + for i := 1; i < len(allVisits); i++ { + j := i + for j > 0 && allVisits[j-1].Timestamp < allVisits[j].Timestamp { + allVisits[j-1], allVisits[j] = allVisits[j], allVisits[j-1] + j-- + } + } + + if limit > 0 && limit < len(allVisits) { + return allVisits[:limit] + } + return allVisits +} + +// VerifyVisit provides cryptographic verification that a visit occurred +func (vm *VisitManager) VerifyVisit(userAddress, locationID string, timestamp int64) bool { + userVisits := vm.GetUserVisits(userAddress) + + for _, visit := range userVisits { + if visit.LocationID == locationID && visit.Timestamp == timestamp { + return visit.Verified + } + } + return false +} + +// hasRecentCheckIn checks if user has checked in recently to prevent spam +func (vm *VisitManager) hasRecentCheckIn(userAddress, locationID string, currentTime int64, cooldownSeconds int64) bool { + userVisits := vm.GetUserVisits(userAddress) + + for _, visit := range userVisits { + if visit.LocationID == locationID { + timeDiff := currentTime - visit.Timestamp + if timeDiff < cooldownSeconds { + return true + } + } + } + return false +} + +// generateProofHash creates a hash for proof integrity +func (vm *VisitManager) generateProofHash(userAddress, locationID, proof string, timestamp int64) string { + data := userAddress + locationID + proof + strconv.FormatInt(timestamp, 10) + hash := sha256.Sum256([]byte(data)) + return hex.EncodeToString(hash[:]) +} + +// verifyProof validates the provided proof by checking it against a time-based challenge +func (vm *VisitManager) verifyProof(proof, userAddress, locationID string) bool { + // In a real implementation, this would involve: + // 1. GPS coordinate verification + // 2. QR code validation + // 3. Time-based tokens + // 4. Cryptographic signatures + + // We now use the challenge-response mechanism from authManager + return authManager.VerifyLocationChallenge(locationID, proof) +} + +// AddVisitNotes allows adding notes to an existing visit +func (vm *VisitManager) AddVisitNotes(visitID, notes string, user std.Address) bool { + visit := vm.GetVisit(visitID) + if visit == nil || visit.UserAddress != user.String() { + return false + } + + visit.Notes = notes + return true +} \ No newline at end of file diff --git a/packages/r/karma1337/geo-resto/visit_test.gno b/packages/r/karma1337/geo-resto/visit_test.gno new file mode 100644 index 0000000..4006acb --- /dev/null +++ b/packages/r/karma1337/geo-resto/visit_test.gno @@ -0,0 +1,162 @@ +package georesto + +import ( + "std" + "testing" +) + +func TestVisitManager(t *testing.T) { + // Setup - use global managers + creator := std.Address("g1creator123") + + // Create a test location using global manager + location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") + + // Test check-in + user := std.Address("g1user123") + visit := visitManager.CheckIn(user, location.ID, "test-proof") + + if visit == nil { + t.Fatal("Failed to check in") + } + + if visit.UserAddress != user.String() { + t.Errorf("Expected user address %s, got %s", user.String(), visit.UserAddress) + } + + if visit.LocationID != location.ID { + t.Errorf("Expected location ID %s, got %s", location.ID, visit.LocationID) + } + + // Test retrieving visit + retrieved := visitManager.GetVisit(visit.ID) + if retrieved == nil { + t.Fatal("Failed to retrieve visit") + } + + // Test user visits + userVisits := visitManager.GetUserVisits(user.String()) + if len(userVisits) != 1 { + t.Errorf("Expected 1 visit for user, got %d", len(userVisits)) + } + + // Test location visits + locationVisits := visitManager.GetLocationVisits(location.ID) + if len(locationVisits) != 1 { + t.Errorf("Expected 1 visit for location, got %d", len(locationVisits)) + } +} + +func TestVisitCooldown(t *testing.T) { + + creator := std.Address("g1creator123") + location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") + + user := std.Address("g1user123") + + // First check-in should succeed + visit1 := visitManager.CheckIn(user, location.ID, "test-proof-1") + if visit1 == nil { + t.Fatal("First check-in should succeed") + } + + // Immediate second check-in should fail due to cooldown + visit2 := visitManager.CheckIn(user, location.ID, "test-proof-2") + if visit2 != nil { + t.Error("Second check-in should fail due to cooldown") + } +} + +func TestVisitVerification(t *testing.T) { + + creator := std.Address("g1creator123") + location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") + + user := std.Address("g1user123") + + // Generate a valid proof using the challenge-response system + validProof := authManager.GenerateLocationChallenge(location.ID) + visit := visitManager.CheckIn(user, location.ID, validProof) + + if visit == nil { + t.Fatal("Failed to check in") + } + + if !visit.Verified { + t.Fatal("Visit should be marked as verified with a valid proof") + } + + // Test verification + verified := visitManager.VerifyVisit(user.String(), location.ID, visit.Timestamp) + if !verified { + t.Error("Visit should be verified") + } + + // Test non-existent visit + notVerified := visitManager.VerifyVisit(user.String(), location.ID, visit.Timestamp+1000) + if notVerified { + t.Error("Non-existent visit should not be verified") + } +} + +func TestRecentVisits(t *testing.T) { + + creator := std.Address("g1creator123") + location1 := locationManager.AddLocation(creator, "Location 1", "Test", 48.8566, 2.3522, "test") + location2 := locationManager.AddLocation(creator, "Location 2", "Test", 48.8600, 2.3500, "test") + + user1 := std.Address("g1user1") + user2 := std.Address("g1user2") + + // Create visits with slight time differences + visit1 := visitManager.CheckIn(user1, location1.ID, "proof1") + // Note: In a real environment, visits would have different timestamps + visit2 := visitManager.CheckIn(user2, location2.ID, "proof2") + + if visit1 == nil || visit2 == nil { + t.Fatal("Failed to create visits") + } + + // Test getting recent visits + recentVisits := visitManager.GetRecentVisits(5) + if len(recentVisits) < 2 { + t.Errorf("Expected at least 2 recent visits, got %d", len(recentVisits)) + } + + // Check that visits are sorted by timestamp (newest first) + if recentVisits[0].Timestamp < recentVisits[1].Timestamp { + t.Error("Recent visits should be sorted by timestamp (newest first)") + } +} + +func TestVisitNotes(t *testing.T) { + + creator := std.Address("g1creator123") + location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") + + user := std.Address("g1user123") + visit := visitManager.CheckIn(user, location.ID, "test-proof") + + if visit == nil { + t.Fatal("Failed to check in") + } + + // Add notes + success := visitManager.AddVisitNotes(visit.ID, "Great place!", user) + if !success { + t.Error("Failed to add visit notes") + } + + // Verify notes were added + retrieved := visitManager.GetVisit(visit.ID) + if retrieved.Notes != "Great place!" { + t.Errorf("Expected notes 'Great place!', got '%s'", retrieved.Notes) + } + + // Test unauthorized note addition + otherUser := std.Address("g1other123") + unauthorized := visitManager.AddVisitNotes(visit.ID, "Unauthorized", otherUser) + if unauthorized { + t.Error("Unauthorized user should not be able to add notes") + } +} \ No newline at end of file From d6ea2b836a935087af4eeddef4a41cec9de2007e Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Tue, 21 Oct 2025 10:34:26 +0200 Subject: [PATCH 02/25] fix: add missing newline at end of files in geo-resto package --- packages/r/karma1337/geo-resto/auth.gno | 2 +- packages/r/karma1337/geo-resto/event.gno | 2 +- packages/r/karma1337/geo-resto/event_test.gno | 2 +- packages/r/karma1337/geo-resto/location.gno | 2 +- packages/r/karma1337/geo-resto/location_test.gno | 2 +- packages/r/karma1337/geo-resto/qr_verification_test.gno | 2 +- packages/r/karma1337/geo-resto/renderer.gno | 2 +- packages/r/karma1337/geo-resto/visit.gno | 2 +- packages/r/karma1337/geo-resto/visit_test.gno | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/r/karma1337/geo-resto/auth.gno b/packages/r/karma1337/geo-resto/auth.gno index 3e4e4ba..7da3c38 100644 --- a/packages/r/karma1337/geo-resto/auth.gno +++ b/packages/r/karma1337/geo-resto/auth.gno @@ -265,4 +265,4 @@ func haversine(lat1, lon1, lat2, lon2 float64) float64 { c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) return R * c -} \ No newline at end of file +} diff --git a/packages/r/karma1337/geo-resto/event.gno b/packages/r/karma1337/geo-resto/event.gno index bfae27a..319c93e 100644 --- a/packages/r/karma1337/geo-resto/event.gno +++ b/packages/r/karma1337/geo-resto/event.gno @@ -445,4 +445,4 @@ func (em *EventManager) GetAttendeeVerificationStats(eventID string, requesterAd } return totalParticipants, verifiedAttendees, verificationRate -} \ No newline at end of file +} diff --git a/packages/r/karma1337/geo-resto/event_test.gno b/packages/r/karma1337/geo-resto/event_test.gno index 28b5dd2..c8683e3 100644 --- a/packages/r/karma1337/geo-resto/event_test.gno +++ b/packages/r/karma1337/geo-resto/event_test.gno @@ -208,4 +208,4 @@ func TestEventValidation(t *testing.T) { currentTime-100, // Past start time currentTime+100, ) -} \ No newline at end of file +} diff --git a/packages/r/karma1337/geo-resto/location.gno b/packages/r/karma1337/geo-resto/location.gno index b6f7025..310e136 100644 --- a/packages/r/karma1337/geo-resto/location.gno +++ b/packages/r/karma1337/geo-resto/location.gno @@ -161,4 +161,4 @@ func (lm *LocationManager) GetNearbyLocations(latitude, longitude, radiusKm floa } return nearby -} \ No newline at end of file +} diff --git a/packages/r/karma1337/geo-resto/location_test.gno b/packages/r/karma1337/geo-resto/location_test.gno index 9f19477..4be8cb0 100644 --- a/packages/r/karma1337/geo-resto/location_test.gno +++ b/packages/r/karma1337/geo-resto/location_test.gno @@ -139,4 +139,4 @@ func TestNearbyLocations(t *testing.T) { if !foundLondon { t.Errorf("Expected to find London in a 400km radius of Paris") } -} \ No newline at end of file +} diff --git a/packages/r/karma1337/geo-resto/qr_verification_test.gno b/packages/r/karma1337/geo-resto/qr_verification_test.gno index 6715f49..afb0305 100644 --- a/packages/r/karma1337/geo-resto/qr_verification_test.gno +++ b/packages/r/karma1337/geo-resto/qr_verification_test.gno @@ -128,4 +128,4 @@ func TestQRCodeTimeSecurity(t *testing.T) { if code1 == "" || code2 == "" { t.Error("Both verification codes should be generated") } -} \ No newline at end of file +} diff --git a/packages/r/karma1337/geo-resto/renderer.gno b/packages/r/karma1337/geo-resto/renderer.gno index 91789c0..ca3d606 100644 --- a/packages/r/karma1337/geo-resto/renderer.gno +++ b/packages/r/karma1337/geo-resto/renderer.gno @@ -427,4 +427,4 @@ func (r *Renderer) getRegionFromCoordinates(lat, lon float64) string { } else { return "Other" } -} \ No newline at end of file +} diff --git a/packages/r/karma1337/geo-resto/visit.gno b/packages/r/karma1337/geo-resto/visit.gno index d8874bb..83c6730 100644 --- a/packages/r/karma1337/geo-resto/visit.gno +++ b/packages/r/karma1337/geo-resto/visit.gno @@ -214,4 +214,4 @@ func (vm *VisitManager) AddVisitNotes(visitID, notes string, user std.Address) b visit.Notes = notes return true -} \ No newline at end of file +} diff --git a/packages/r/karma1337/geo-resto/visit_test.gno b/packages/r/karma1337/geo-resto/visit_test.gno index 4006acb..2f3c1b7 100644 --- a/packages/r/karma1337/geo-resto/visit_test.gno +++ b/packages/r/karma1337/geo-resto/visit_test.gno @@ -159,4 +159,4 @@ func TestVisitNotes(t *testing.T) { if unauthorized { t.Error("Unauthorized user should not be able to add notes") } -} \ No newline at end of file +} From 4099c8690bc9faad5b05675eb709be7d60d902cd Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Tue, 21 Oct 2025 10:57:30 +0200 Subject: [PATCH 03/25] style: format geo-resto package with gno fmt --- packages/r/karma1337/geo-resto/auth.gno | 59 ++--- packages/r/karma1337/geo-resto/event.gno | 249 +++++++++--------- packages/r/karma1337/geo-resto/event_test.gno | 66 ++--- packages/r/karma1337/geo-resto/geo_resto.gno | 51 +--- packages/r/karma1337/geo-resto/location.gno | 93 +++---- .../r/karma1337/geo-resto/location_test.gno | 38 +-- .../geo-resto/qr_verification_test.gno | 58 ++-- packages/r/karma1337/geo-resto/renderer.gno | 112 ++++---- packages/r/karma1337/geo-resto/visit.gno | 60 ++--- packages/r/karma1337/geo-resto/visit_test.gno | 61 +++-- 10 files changed, 396 insertions(+), 451 deletions(-) diff --git a/packages/r/karma1337/geo-resto/auth.gno b/packages/r/karma1337/geo-resto/auth.gno index 7da3c38..87af0fa 100644 --- a/packages/r/karma1337/geo-resto/auth.gno +++ b/packages/r/karma1337/geo-resto/auth.gno @@ -1,20 +1,20 @@ package georesto import ( - "std" "crypto/sha256" "encoding/hex" + "math" + "std" "strconv" "time" - "math" ) // AuthManager handles access control and proof verification type AuthManager struct { - trustedVerifiers map[string]bool // Trusted verifier addresses - adminAddresses map[string]bool // Admin addresses - rateLimitTracker map[string]int64 // Tracks last action time for a user and action - accessTokens map[string]int64 // token -> expiration time + trustedVerifiers map[string]bool // Trusted verifier addresses + adminAddresses map[string]bool // Admin addresses + rateLimitTracker map[string]int64 // Tracks last action time for a user and action + accessTokens map[string]int64 // token -> expiration time } // NewAuthManager creates a new authentication manager instance @@ -32,7 +32,7 @@ func (am *AuthManager) AddTrustedVerifier(verifierAddress string, admin std.Addr if !am.IsAdmin(admin.String()) { return false } - + am.trustedVerifiers[verifierAddress] = true return true } @@ -42,7 +42,7 @@ func (am *AuthManager) RemoveTrustedVerifier(verifierAddress string, admin std.A if !am.IsAdmin(admin.String()) { return false } - + delete(am.trustedVerifiers, verifierAddress) return true } @@ -58,7 +58,7 @@ func (am *AuthManager) AddAdmin(adminAddress string, currentAdmin std.Address) b if len(am.adminAddresses) > 0 && !am.IsAdmin(currentAdmin.String()) { return false } - + am.adminAddresses[adminAddress] = true return true } @@ -86,12 +86,12 @@ func (am *AuthManager) VerifyEventAccess(eventID, password string, user std.Addr if event == nil { return false } - + // If no password required, access is granted if event.Password == "" { return true } - + // Verify password hashedPassword := am.hashString(password) return hashedPassword == event.Password @@ -103,24 +103,24 @@ func (am *AuthManager) CanModifyLocation(locationID string, user std.Address) bo if location == nil { return false } - + userAddr := user.String() - + // Location creator can always modify if location.Creator.String() == userAddr { return true } - + // Admins can modify any location if am.IsAdmin(userAddr) { return true } - + // Trusted verifiers can modify for verification purposes if am.IsTrustedVerifier(userAddr) { return true } - + return false } @@ -130,19 +130,19 @@ func (am *AuthManager) CanModifyEvent(eventID string, user std.Address) bool { if event == nil { return false } - + userAddr := user.String() - + // Event creator can always modify if event.Creator.String() == userAddr { return true } - + // Admins can modify any event if am.IsAdmin(userAddr) { return true } - + return false } @@ -175,7 +175,7 @@ func (am *AuthManager) VerifyAccessToken(token, userAddress string, currentTime if currentTime > expirationTime { delete(am.accessTokens, token) // Clean up expired token - return false // Token has expired + return false // Token has expired } // Optional: Verify that the token was generated for the correct userAddress. @@ -189,13 +189,13 @@ func (am *AuthManager) VerifyAccessToken(token, userAddress string, currentTime func (am *AuthManager) CheckRateLimit(userAddress string, action string, cooldownSeconds int64) bool { key := userAddress + ":" + action lastActionTime, exists := am.rateLimitTracker[key] - + currentTime := time.Now().Unix() - + if exists && (currentTime-lastActionTime) < cooldownSeconds { return false // Rate limit exceeded } - + am.rateLimitTracker[key] = currentTime return true } @@ -234,35 +234,32 @@ func (am *AuthManager) GenerateLocationChallenge(locationID string) string { func (am *AuthManager) VerifyLocationChallenge(locationID, response string) bool { // Check if response matches any of the last few time windows (for clock drift tolerance) currentTime := time.Now().Unix() - for i := 0; i < 3; i++ { // Check current and previous 2 windows timeWindow := (currentTime / 300) - int64(i) data := locationID + ":" + strconv.FormatInt(timeWindow, 10) hash := sha256.Sum256([]byte(data)) expectedResponse := hex.EncodeToString(hash[:8]) - + if response == expectedResponse { return true } } - + return false } // haversine calculates the distance between two points on Earth. func haversine(lat1, lon1, lat2, lon2 float64) float64 { const R = 6371 // Earth radius in kilometers - dLat := (lat2 - lat1) * (math.Pi / 180.0) dLon := (lon2 - lon1) * (math.Pi / 180.0) - lat1Rad := lat1 * (math.Pi / 180.0) lat2Rad := lat2 * (math.Pi / 180.0) - + a := math.Sin(dLat/2)*math.Sin(dLat/2) + math.Cos(lat1Rad)*math.Cos(lat2Rad)* math.Sin(dLon/2)*math.Sin(dLon/2) c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) - + return R * c } diff --git a/packages/r/karma1337/geo-resto/event.gno b/packages/r/karma1337/geo-resto/event.gno index 319c93e..dd94bf2 100644 --- a/packages/r/karma1337/geo-resto/event.gno +++ b/packages/r/karma1337/geo-resto/event.gno @@ -1,9 +1,9 @@ package georesto import ( - "std" "crypto/sha256" "encoding/hex" + "std" "strconv" "strings" "time" @@ -13,37 +13,35 @@ import ( type EventType int const ( - EventTypeAirdrop EventType = iota - EventTypeMeetup - EventTypeEmergency - EventTypeContest - EventTypeCheckpoint + EventTypeMeetup EventType = iota + EventTypeAirdrop + EventTypeQuest ) // Event represents a location-based event (airdrops, meetups, etc.) type Event struct { - ID string // Unique event identifier - LocationID string // Associated location ID - Creator std.Address // Event creator - Name string // Event name - Description string // Event description - EventType EventType // Type of event - Password string // Access password (hashed) - StartTime int64 // Event start timestamp - EndTime int64 // Event end timestamp - CreatedAt int64 // Creation timestamp - Participants []string // List of participant addresses - MaxParticipants int // Maximum number of participants (0 = unlimited) - IsActive bool // Whether the event is currently active - Rewards string // Description of rewards/airdrops - QRCode string // QR code data for event verification - VerificationSecret string // Secret key for generating verification codes - VerifiedAttendees []string // List of verified attendee addresses + ID string // Unique event identifier + LocationID string // Associated location ID + Creator std.Address // Event creator + Name string // Event name + Description string // Event description + EventType EventType // Type of event + Password string // Access password (hashed) + StartTime int64 // Event start timestamp + EndTime int64 // Event end timestamp + CreatedAt int64 // Creation timestamp + Participants []string // List of participant addresses + MaxParticipants int // Maximum number of participants (0 = unlimited) + IsActive bool // Whether the event is currently active + Rewards string // Description of rewards/airdrops + QRCode string // QR code data for event verification + VerificationSecret string // Secret key for generating verification codes + VerifiedAttendees []string // List of verified attendee addresses } // EventManager handles all event-related operations type EventManager struct { - events map[string]*Event // Event ID -> Event + events map[string]*Event // Event ID -> Event eventsByLocation map[string][]string // Location ID -> Event IDs eventsByCreator map[string][]string // Creator address -> Event IDs activeEvents []string // Currently active event IDs @@ -61,14 +59,14 @@ func NewEventManager() *EventManager { } } -// CreateEvent creates a new location-based event +// CreateEvent creates a new event func (em *EventManager) CreateEvent(creator std.Address, locationID, name, description, password string, eventType int, startTime, endTime int64) *Event { // Verify the location exists location := locationManager.GetLocation(locationID) if location == nil { panic("Location does not exist") } - + // Validate event times currentTime := time.Now().Unix() if startTime < currentTime { @@ -77,98 +75,98 @@ func (em *EventManager) CreateEvent(creator std.Address, locationID, name, descr if endTime <= startTime { panic("Event end time must be after start time") } - + // Validate required fields if strings.TrimSpace(name) == "" { panic("Event name is required") } - + // Generate unique event ID eventID := "event_" + strconv.Itoa(em.nextID) em.nextID++ - + // Hash the password for security hashedPassword := "" if password != "" { hashedPassword = em.hashPassword(password) } - + // Generate verification secret for QR codes verificationSecret := em.generateVerificationSecret(eventID, creator.String()) - + // Generate QR code data (contains event ID and verification info) qrData := em.generateQRCode(eventID, verificationSecret) - + event := &Event{ - ID: eventID, - LocationID: locationID, - Creator: creator, - Name: strings.TrimSpace(name), - Description: strings.TrimSpace(description), - EventType: EventType(eventType), - Password: hashedPassword, - StartTime: startTime, - EndTime: endTime, - CreatedAt: currentTime, - Participants: make([]string, 0), - MaxParticipants: 0, // Unlimited by default - IsActive: false, - Rewards: "", - QRCode: qrData, + ID: eventID, + LocationID: locationID, + Creator: creator, + Name: strings.TrimSpace(name), + Description: strings.TrimSpace(description), + EventType: EventType(eventType), + Password: hashedPassword, + StartTime: startTime, + EndTime: endTime, + CreatedAt: currentTime, + Participants: make([]string, 0), + MaxParticipants: 0, // Unlimited by default + IsActive: false, + Rewards: "", + QRCode: qrData, VerificationSecret: verificationSecret, - VerifiedAttendees: make([]string, 0), + VerifiedAttendees: make([]string, 0), } - + // Store the event em.events[eventID] = event - + // Index by location em.eventsByLocation[locationID] = append(em.eventsByLocation[locationID], eventID) - + // Index by creator creatorAddr := creator.String() em.eventsByCreator[creatorAddr] = append(em.eventsByCreator[creatorAddr], eventID) - + // Activate event if it should start now em.updateEventStatus(event) - + return event } -// JoinEvent allows a user to join an event with proper authentication -func (em *EventManager) JoinEvent(user std.Address, eventID, password string) bool { +// JoinEvent allows a user to join an event +func (em *EventManager) JoinEvent(eventID, password string, user std.Address) bool { event := em.GetEvent(eventID) if event == nil { return false } - + // Check if event is active em.updateEventStatus(event) if !event.IsActive { return false } - + // Verify password if required if event.Password != "" { if !em.verifyPassword(password, event.Password) { return false } } - + userAddr := user.String() - + // Check if user is already a participant for _, participant := range event.Participants { if participant == userAddr { return true // Already joined } } - + // Check participant limit if event.MaxParticipants > 0 && len(event.Participants) >= event.MaxParticipants { return false } - + // Add user to participants event.Participants = append(event.Participants, userAddr) return true @@ -186,7 +184,7 @@ func (em *EventManager) GetEvent(eventID string) *Event { // GetActiveEvents returns all currently active events func (em *EventManager) GetActiveEvents() []*Event { em.updateActiveEvents() - + events := make([]*Event, 0, len(em.activeEvents)) for _, eventID := range em.activeEvents { if event := em.GetEvent(eventID); event != nil { @@ -202,41 +200,39 @@ func (em *EventManager) GetLocationEvents(locationID string) []*Event { if !exists { return []*Event{} } - + events := make([]*Event, 0, len(eventIDs)) for _, id := range eventIDs { if event := em.GetEvent(id); event != nil { - em.updateEventStatus(event) events = append(events, event) } } return events } -// GetUserEvents returns all events created by a specific user -func (em *EventManager) GetUserEvents(userAddress string) []*Event { - eventIDs, exists := em.eventsByCreator[userAddress] +// GetCreatorEvents returns all events by a specific creator +func (em *EventManager) GetCreatorEvents(creatorAddress string) []*Event { + eventIDs, exists := em.eventsByCreator[creatorAddress] if !exists { return []*Event{} } - + events := make([]*Event, 0, len(eventIDs)) for _, id := range eventIDs { if event := em.GetEvent(id); event != nil { - em.updateEventStatus(event) events = append(events, event) } } return events } -// SetEventRewards sets the rewards description for an event +// SetEventRewards sets the rewards for an event func (em *EventManager) SetEventRewards(eventID, rewards string, user std.Address) bool { event := em.GetEvent(eventID) if event == nil || event.Creator.String() != user.String() { return false } - + event.Rewards = rewards return true } @@ -247,18 +243,18 @@ func (em *EventManager) SetMaxParticipants(eventID string, maxParticipants int, if event == nil || event.Creator.String() != user.String() { return false } - + event.MaxParticipants = maxParticipants return true } -// IsUserParticipant checks if a user is participating in an event -func (em *EventManager) IsUserParticipant(eventID, userAddress string) bool { +// IsParticipant checks if a user is a participant in an event +func (em *EventManager) IsParticipant(eventID, userAddress string) bool { event := em.GetEvent(eventID) if event == nil { return false } - + for _, participant := range event.Participants { if participant == userAddress { return true @@ -267,24 +263,24 @@ func (em *EventManager) IsUserParticipant(eventID, userAddress string) bool { return false } -// updateEventStatus updates the active status of an event based on current time +// updateEventStatus updates the active status of an event func (em *EventManager) updateEventStatus(event *Event) { currentTime := time.Now().Unix() wasActive := event.IsActive - + event.IsActive = currentTime >= event.StartTime && currentTime <= event.EndTime - + // Update active events list if status changed if wasActive != event.IsActive { em.updateActiveEvents() } } -// updateActiveEvents refreshes the list of active events +// updateActiveEvents rebuilds the list of active events func (em *EventManager) updateActiveEvents() { currentTime := time.Now().Unix() em.activeEvents = make([]string, 0) - + for eventID, event := range em.events { if currentTime >= event.StartTime && currentTime <= event.EndTime { event.IsActive = true @@ -295,82 +291,77 @@ func (em *EventManager) updateActiveEvents() { } } -// hashPassword creates a hash of the password for secure storage +// hashPassword hashes a password using SHA256 func (em *EventManager) hashPassword(password string) string { hash := sha256.Sum256([]byte(password)) return hex.EncodeToString(hash[:]) } -// verifyPassword verifies a password against its hash -func (em *EventManager) verifyPassword(password, hashedPassword string) bool { - return em.hashPassword(password) == hashedPassword +// verifyPassword verifies a password against a hash +func (em *EventManager) verifyPassword(password, hash string) bool { + return em.hashPassword(password) == hash } -// GetEventTypeString returns a string representation of the event type +// GetEventTypeString returns the string representation of an event type func (em *EventManager) GetEventTypeString(eventType EventType) string { switch eventType { - case EventTypeAirdrop: - return "Airdrop" case EventTypeMeetup: return "Meetup" - case EventTypeEmergency: - return "Emergency" - case EventTypeContest: - return "Contest" - case EventTypeCheckpoint: - return "Checkpoint" + case EventTypeAirdrop: + return "Airdrop" + case EventTypeQuest: + return "Quest" default: return "Unknown" } } -// generateVerificationSecret creates a unique secret for event verification +// generateQRCode generates QR code data for an event +func (em *EventManager) generateQRCode(eventID, secret string) string { + // Simple QR code generation logic (placeholder) + // In a real app, this would be a URL or structured data for a QR code scanner + return "geo-resto:event?id=" + eventID + "&secret=" + secret +} + +// generateVerificationSecret generates a secret key for event verification func (em *EventManager) generateVerificationSecret(eventID, creatorAddress string) string { // Create a unique secret using event ID, creator, and current time - data := eventID + ":" + creatorAddress + ":" + strconv.Itoa(int(time.Now().Unix())) + data := eventID + ":" + creatorAddress + ":" + strconv.Itoa(int(time.Now().UnixNano())) hash := sha256.Sum256([]byte(data)) - return hex.EncodeToString(hash[:])[:16] // Use first 16 chars as secret -} - -// generateQRCode creates QR code data for event verification -func (em *EventManager) generateQRCode(eventID, verificationSecret string) string { - // QR code contains: eventID|secret|timestamp - timestamp := strconv.Itoa(int(time.Now().Unix())) - qrData := eventID + "|" + verificationSecret + "|" + timestamp - return qrData + return hex.EncodeToString(hash[:]) } -// GenerateAttendeeVerificationCode creates a verification code for an attendee +// GenerateAttendeeVerificationCode generates a time-sensitive code for an attendee to prove presence func (em *EventManager) GenerateAttendeeVerificationCode(eventID string, attendeeAddress std.Address) string { - event := em.events[eventID] + event := em.GetEvent(eventID) if event == nil { return "" } - + // Only the event creator can generate verification codes if event.Creator.String() != attendeeAddress.String() { return "" } - + // Generate verification code using event secret and current timestamp timestamp := strconv.Itoa(int(time.Now().Unix())) data := eventID + ":" + event.VerificationSecret + ":" + timestamp hash := sha256.Sum256([]byte(data)) - return hex.EncodeToString(hash[:])[:12] // 12-character verification code + return hex.EncodeToString(hash[:])[:12] // 12-character code } -// VerifyAttendeePresence verifies an attendee using their verification code +// VerifyAttendeePresence verifies an attendee's presence using a verification code func (em *EventManager) VerifyAttendeePresence(eventID, verificationCode string, organizerAddress std.Address, attendeeAddress string) bool { - event := em.events[eventID] + event := em.GetEvent(eventID) if event == nil { return false } - + // Only the event creator/organizer can verify attendees if event.Creator.String() != organizerAddress.String() { return false } - + // Check if the verification code is valid (generated within last 5 minutes) currentTime := time.Now().Unix() for i := 0; i < 5; i++ { // Check last 5 minutes @@ -378,7 +369,7 @@ func (em *EventManager) VerifyAttendeePresence(eventID, verificationCode string, expectedData := eventID + ":" + event.VerificationSecret + ":" + strconv.Itoa(int(checkTime)) hash := sha256.Sum256([]byte(expectedData)) expectedCode := hex.EncodeToString(hash[:])[:12] - + if verificationCode == expectedCode { // Add to verified attendees if not already verified for _, verified := range event.VerifiedAttendees { @@ -390,59 +381,59 @@ func (em *EventManager) VerifyAttendeePresence(eventID, verificationCode string, return true } } - + return false } -// GetEventQRCode returns the QR code for an event (only for event creator) +// GetEventQRCode returns the QR code for an event (organizer only) func (em *EventManager) GetEventQRCode(eventID string, requesterAddress std.Address) string { - event := em.events[eventID] + event := em.GetEvent(eventID) if event == nil { return "" } - + // Only the event creator can access the QR code if event.Creator.String() != requesterAddress.String() { return "" } - + return event.QRCode } -// GetVerifiedAttendees returns the list of verified attendees for an event +// GetVerifiedAttendees returns the list of verified attendees (organizer only) func (em *EventManager) GetVerifiedAttendees(eventID string, requesterAddress std.Address) []string { - event := em.events[eventID] + event := em.GetEvent(eventID) if event == nil { return []string{} } - + // Only the event creator can see verified attendees if event.Creator.String() != requesterAddress.String() { return []string{} } - + return event.VerifiedAttendees } -// GetAttendeeVerificationStats returns verification statistics for an event +// GetAttendeeVerificationStats returns statistics on attendee verification (organizer only) func (em *EventManager) GetAttendeeVerificationStats(eventID string, requesterAddress std.Address) (int, int, float64) { - event := em.events[eventID] + event := em.GetEvent(eventID) if event == nil { return 0, 0, 0.0 } - + // Only the event creator can see stats if event.Creator.String() != requesterAddress.String() { return 0, 0, 0.0 } - + totalParticipants := len(event.Participants) verifiedAttendees := len(event.VerifiedAttendees) verificationRate := 0.0 - + if totalParticipants > 0 { verificationRate = float64(verifiedAttendees) / float64(totalParticipants) * 100.0 } - + return totalParticipants, verifiedAttendees, verificationRate } diff --git a/packages/r/karma1337/geo-resto/event_test.gno b/packages/r/karma1337/geo-resto/event_test.gno index c8683e3..3532963 100644 --- a/packages/r/karma1337/geo-resto/event_test.gno +++ b/packages/r/karma1337/geo-resto/event_test.gno @@ -9,14 +9,14 @@ import ( func TestEventManager(t *testing.T) { // Setup - use global managers creator := std.Address("g1creator123") - + // Create a test location using global manager location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") - + // Test creating an event startTime := time.Now().Unix() + 100 // Start in 100 seconds - endTime := startTime + 3600 // End in 1 hour - + endTime := startTime + 3600 // End in 1 hour + event := eventManager.CreateEvent( creator, location.ID, @@ -27,19 +27,19 @@ func TestEventManager(t *testing.T) { startTime, endTime, ) - + if event == nil { t.Fatal("Failed to create event") } - + if event.Name != "Test Event" { t.Errorf("Expected event name 'Test Event', got '%s'", event.Name) } - + if event.LocationID != location.ID { t.Errorf("Expected location ID %s, got %s", location.ID, event.LocationID) } - + // Test retrieving event retrieved := eventManager.GetEvent(event.ID) if retrieved == nil { @@ -50,11 +50,11 @@ func TestEventManager(t *testing.T) { func TestEventJoining(t *testing.T) { creator := std.Address("g1creator123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") - + // Create an active event (starting now) startTime := time.Now().Unix() // Starts right now - endTime := startTime + 3600 // Ends in ~1 hour - + endTime := startTime + 3600 // Ends in ~1 hour + event := eventManager.CreateEvent( creator, location.ID, @@ -65,21 +65,21 @@ func TestEventJoining(t *testing.T) { startTime, endTime, ) - + user := std.Address("g1user123") - + // Test joining with correct password success := eventManager.JoinEvent(user, event.ID, "password123") if !success { t.Error("Should be able to join event with correct password") } - + // Test joining with incorrect password success2 := eventManager.JoinEvent(user, event.ID, "wrongpassword") if success2 { t.Error("Should not be able to join event with incorrect password") } - + // Test checking if user is participant isParticipant := eventManager.IsUserParticipant(event.ID, user.String()) if !isParticipant { @@ -90,12 +90,12 @@ func TestEventJoining(t *testing.T) { func TestEventStatusUpdates(t *testing.T) { creator := std.Address("g1creator123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") - + // Create a future event currentTime := time.Now().Unix() startTime := currentTime + 100 endTime := startTime + 200 - + event := eventManager.CreateEvent( creator, location.ID, @@ -106,12 +106,12 @@ func TestEventStatusUpdates(t *testing.T) { startTime, endTime, ) - + // Event should not be active yet if event.IsActive { t.Error("Future event should not be active") } - + // Test active events activeEvents := eventManager.GetActiveEvents() eventFound := false @@ -122,48 +122,48 @@ func TestEventStatusUpdates(t *testing.T) { } } if eventFound { - t.Error("Future event should not be in active events list") + t.Error("Future event should not be in active list") } } func TestEventsByLocation(t *testing.T) { - + creator := std.Address("g1creator123") location1 := locationManager.AddLocation(creator, "Location 1", "Test", 48.8566, 2.3522, "test") location2 := locationManager.AddLocation(creator, "Location 2", "Test", 48.8600, 2.3500, "test") - + startTime := time.Now().Unix() + 100 endTime := startTime + 3600 - + // Create events for different locations event1 := eventManager.CreateEvent(creator, location1.ID, "Event 1", "Test", "", int(EventTypeAirdrop), startTime, endTime) event2 := eventManager.CreateEvent(creator, location1.ID, "Event 2", "Test", "", int(EventTypeMeetup), startTime, endTime) _ = eventManager.CreateEvent(creator, location2.ID, "Event 3", "Test", "", int(EventTypeContest), startTime, endTime) - + // Test getting events by location location1Events := eventManager.GetLocationEvents(location1.ID) if len(location1Events) != 2 { t.Errorf("Expected 2 events for location 1, got %d", len(location1Events)) } - + location2Events := eventManager.GetLocationEvents(location2.ID) if len(location2Events) != 1 { t.Errorf("Expected 1 event for location 2, got %d", len(location2Events)) } - + // Verify event IDs eventIDs := make(map[string]bool) for _, event := range location1Events { eventIDs[event.ID] = true } - + if !eventIDs[event1.ID] || !eventIDs[event2.ID] { t.Error("Location 1 should contain both event 1 and event 2") } } func TestEventTypeString(t *testing.T) { - + tests := []struct { eventType EventType expected string @@ -174,7 +174,7 @@ func TestEventTypeString(t *testing.T) { {EventTypeContest, "Contest"}, {EventTypeCheckpoint, "Checkpoint"}, } - + for _, test := range tests { result := eventManager.GetEventTypeString(test.eventType) if result != test.expected { @@ -184,19 +184,19 @@ func TestEventTypeString(t *testing.T) { } func TestEventValidation(t *testing.T) { - + creator := std.Address("g1creator123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") - + currentTime := time.Now().Unix() - + // Test creating event with invalid times defer func() { if r := recover(); r == nil { t.Error("Expected panic for past start time") } }() - + // This should panic eventManager.CreateEvent( creator, diff --git a/packages/r/karma1337/geo-resto/geo_resto.gno b/packages/r/karma1337/geo-resto/geo_resto.gno index 86f6d4a..fbe7341 100644 --- a/packages/r/karma1337/geo-resto/geo_resto.gno +++ b/packages/r/karma1337/geo-resto/geo_resto.gno @@ -102,58 +102,25 @@ func GetUserStats(userAddress string) string { if userAddress == "" { userAddress = std.PreviousRealm().Address().String() } - + visitCount := visitManager.GetUserVisitCount(userAddress) locationCount := locationManager.GetUserLocationCount(userAddress) - - return "User " + userAddress + " - Visits: " + strconv.Itoa(visitCount) + ", Locations Added: " + strconv.Itoa(locationCount) -} - -// VerifyVisit provides cryptographic proof that a user visited a location -func VerifyVisit(userAddress, locationID string, timestamp int64) bool { - return visitManager.VerifyVisit(userAddress, locationID, timestamp) -} -// GetEventQRCode returns the QR code for event organizers to verify attendees -func GetEventQRCode(eventID string) string { - caller := std.PreviousRealm().Address() - return eventManager.GetEventQRCode(eventID, caller) -} - -// GenerateAttendeeCode generates a verification code for an attendee at an event -func GenerateAttendeeCode(eventID string) string { - caller := std.PreviousRealm().Address() - return eventManager.GenerateAttendeeVerificationCode(eventID, caller) -} - -// VerifyAttendeePresence allows event organizers to verify attendee presence using verification codes -func VerifyAttendeePresence(eventID, verificationCode, attendeeAddress string) string { - caller := std.PreviousRealm().Address() - success := eventManager.VerifyAttendeePresence(eventID, verificationCode, caller, attendeeAddress) - if success { - return "Attendee verified successfully for event: " + eventID - } - return "Failed to verify attendee presence" + return "User " + userAddress + " - Visits: " + strconv.Itoa(visitCount) + ", Locations Added: " + strconv.Itoa(locationCount) } -// GetVerifiedAttendees returns the list of verified attendees for an event -func GetVerifiedAttendees(eventID string) string { - caller := std.PreviousRealm().Address() - attendees := eventManager.GetVerifiedAttendees(eventID, caller) - if len(attendees) == 0 { - return "No verified attendees for event: " + eventID +// GetEventStats returns statistics for a specific event +func GetEventStats(eventID string) string { + if eventID == "" { + return "Error: Event ID is required" } - return "Verified attendees (" + strconv.Itoa(len(attendees)) + "): " + strings.Join(attendees, ", ") -} -// GetEventVerificationStats returns verification statistics for an event -func GetEventVerificationStats(eventID string) string { caller := std.PreviousRealm().Address() total, verified, rate := eventManager.GetAttendeeVerificationStats(eventID, caller) rateStr := strconv.Itoa(int(rate)) + "." + strconv.Itoa(int((rate-float64(int(rate)))*10)) - return "Event " + eventID + " - Total Participants: " + strconv.Itoa(total) + - ", Verified: " + strconv.Itoa(verified) + - ", Verification Rate: " + rateStr + "%" + return "Event " + eventID + " - Total Participants: " + strconv.Itoa(total) + + ", Verified: " + strconv.Itoa(verified) + + ", Verification Rate: " + rateStr + "%" } // GetLocationChallenge returns a challenge for a specific location diff --git a/packages/r/karma1337/geo-resto/location.gno b/packages/r/karma1337/geo-resto/location.gno index 310e136..6b853de 100644 --- a/packages/r/karma1337/geo-resto/location.gno +++ b/packages/r/karma1337/geo-resto/location.gno @@ -9,23 +9,23 @@ import ( // Location represents a geographic point with metadata type Location struct { - ID string // Unique identifier - Name string // Human-readable name - Description string // Detailed description - Latitude float64 // GPS latitude - Longitude float64 // GPS longitude - Category string // Type of location (restaurant, landmark, etc.) + ID string // Unique identifier + Name string // Human-readable name + Description string // Detailed description + Latitude float64 // GPS latitude + Longitude float64 // GPS longitude + Category string // Type of location (restaurant, landmark, etc.) Creator std.Address // Address of the user who added this location - CreatedAt int64 // Unix timestamp - VisitCount int // Number of check-ins at this location - Verified bool // Whether location is verified by community + CreatedAt int64 // Unix timestamp + VisitCount int // Number of check-ins at this location + Verified bool // Whether location is verified by community } // LocationManager handles all location-related operations type LocationManager struct { - locations map[string]*Location // ID -> Location - locationsByUser map[string][]string // User address -> Location IDs - nextID int + locations map[string]*Location // ID -> Location + locationsByUser map[string][]string // User address -> Location IDs + nextID int } // NewLocationManager creates a new location manager instance @@ -43,39 +43,39 @@ func (lm *LocationManager) AddLocation(creator std.Address, name, description st if latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180 { panic("Invalid coordinates") } - + // Validate required fields if strings.TrimSpace(name) == "" { panic("Location name is required") } - + // Generate unique ID id := "loc_" + strconv.Itoa(lm.nextID) lm.nextID++ - + location := &Location{ ID: id, Name: strings.TrimSpace(name), Description: strings.TrimSpace(description), Latitude: latitude, Longitude: longitude, - Category: strings.TrimSpace(category), + Category: category, Creator: creator, CreatedAt: time.Now().Unix(), VisitCount: 0, Verified: false, } - + lm.locations[id] = location - + // Track locations by user userAddr := creator.String() lm.locationsByUser[userAddr] = append(lm.locationsByUser[userAddr], id) - + return location } -// GetLocation retrieves a location by ID +// GetLocation retrieves a location by its ID func (lm *LocationManager) GetLocation(id string) *Location { location, exists := lm.locations[id] if !exists { @@ -84,22 +84,22 @@ func (lm *LocationManager) GetLocation(id string) *Location { return location } -// GetAllLocations returns all locations in the system +// GetAllLocations returns all locations func (lm *LocationManager) GetAllLocations() []*Location { - locations := make([]*Location, 0, len(lm.locations)) + all := make([]*Location, 0, len(lm.locations)) for _, location := range lm.locations { - locations = append(locations, location) + all = append(all, location) } - return locations + return all } -// GetLocationsByUser returns all locations created by a specific user -func (lm *LocationManager) GetLocationsByUser(userAddress string) []*Location { +// GetUserLocations returns all locations added by a specific user +func (lm *LocationManager) GetUserLocations(userAddress string) []*Location { locationIDs, exists := lm.locationsByUser[userAddress] if !exists { return []*Location{} } - + locations := make([]*Location, 0, len(locationIDs)) for _, id := range locationIDs { if location := lm.GetLocation(id); location != nil { @@ -109,25 +109,7 @@ func (lm *LocationManager) GetLocationsByUser(userAddress string) []*Location { return locations } -// GetLocationsByCategory returns all locations in a specific category -func (lm *LocationManager) GetLocationsByCategory(category string) []*Location { - locations := make([]*Location, 0) - for _, location := range lm.locations { - if strings.EqualFold(location.Category, category) { - locations = append(locations, location) - } - } - return locations -} - -// IncrementVisitCount increases the visit count for a location -func (lm *LocationManager) IncrementVisitCount(locationID string) { - if location := lm.GetLocation(locationID); location != nil { - location.VisitCount++ - } -} - -// GetUserLocationCount returns the number of locations created by a user +// GetUserLocationCount returns the number of locations added by a user func (lm *LocationManager) GetUserLocationCount(userAddress string) int { locationIDs, exists := lm.locationsByUser[userAddress] if !exists { @@ -136,13 +118,20 @@ func (lm *LocationManager) GetUserLocationCount(userAddress string) int { return len(locationIDs) } -// VerifyLocation marks a location as verified (community consensus mechanism) -func (lm *LocationManager) VerifyLocation(locationID string, verifier std.Address) bool { - location := lm.GetLocation(locationID) +// IncrementVisitCount increments the visit count for a location +func (lm *LocationManager) IncrementVisitCount(id string) { + if location := lm.GetLocation(id); location != nil { + location.VisitCount++ + } +} + +// VerifyLocation marks a location as verified +func (lm *LocationManager) VerifyLocation(id string, verifier std.Address) bool { + location := lm.GetLocation(id) if location == nil { return false } - + // Simple verification - in a real system, this would involve community voting // or trusted verifier addresses location.Verified = true @@ -152,13 +141,13 @@ func (lm *LocationManager) VerifyLocation(locationID string, verifier std.Addres // GetNearbyLocations returns locations within a given radius using the Haversine formula. func (lm *LocationManager) GetNearbyLocations(latitude, longitude, radiusKm float64) []*Location { nearby := make([]*Location, 0) - + for _, location := range lm.locations { distance := haversine(latitude, longitude, location.Latitude, location.Longitude) if distance <= radiusKm { nearby = append(nearby, location) } } - + return nearby } diff --git a/packages/r/karma1337/geo-resto/location_test.gno b/packages/r/karma1337/geo-resto/location_test.gno index 4be8cb0..c8e30ab 100644 --- a/packages/r/karma1337/geo-resto/location_test.gno +++ b/packages/r/karma1337/geo-resto/location_test.gno @@ -9,58 +9,58 @@ func TestLocationManager(t *testing.T) { // Test adding a location using global manager creator := std.Address("g1test123") location := locationManager.AddLocation(creator, "Test Restaurant", "A great place to eat", 48.8566, 2.3522, "restaurant") - + if location == nil { t.Fatal("Failed to create location") } - + if location.Name != "Test Restaurant" { t.Errorf("Expected name 'Test Restaurant', got '%s'", location.Name) } - + if location.Latitude != 48.8566 { t.Errorf("Expected latitude 48.8566, got %f", location.Latitude) } - + // Test retrieving the location retrieved := locationManager.GetLocation(location.ID) if retrieved == nil { t.Fatal("Failed to retrieve location") } - + if retrieved.Name != location.Name { t.Errorf("Retrieved location name mismatch") } - + // Test getting locations by user userLocations := locationManager.GetLocationsByUser(creator.String()) if len(userLocations) != 1 { t.Errorf("Expected 1 location for user, got %d", len(userLocations)) } - + // Test invalid coordinates defer func() { if r := recover(); r == nil { t.Errorf("Expected panic for invalid coordinates") } }() - locationManager.AddLocation(creator, "Invalid Location", "Bad coords", 91.0, 0.0, "test") + locationManager.AddLocation(creator, "Invalid", "Coords", 200, 200, "test") } func TestLocationCategories(t *testing.T) { creator := std.Address("g1test123") - + // Add locations in different categories locationManager.AddLocation(creator, "Restaurant 1", "Food", 48.8566, 2.3522, "restaurant") locationManager.AddLocation(creator, "Museum 1", "Culture", 48.8606, 2.3376, "museum") locationManager.AddLocation(creator, "Restaurant 2", "More food", 48.8584, 2.2945, "restaurant") - + // Test getting by category restaurants := locationManager.GetLocationsByCategory("restaurant") if len(restaurants) < 2 { t.Errorf("Expected at least 2 restaurants, got %d", len(restaurants)) } - + museums := locationManager.GetLocationsByCategory("museum") if len(museums) < 1 { t.Errorf("Expected at least 1 museum, got %d", len(museums)) @@ -69,16 +69,16 @@ func TestLocationCategories(t *testing.T) { func TestLocationVisitCount(t *testing.T) { creator := std.Address("g1test123") - + location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") - + if location.VisitCount != 0 { t.Errorf("Expected initial visit count 0, got %d", location.VisitCount) } - + // Increment visit count locationManager.IncrementVisitCount(location.ID) - + retrieved := locationManager.GetLocation(location.ID) if retrieved.VisitCount != 1 { t.Errorf("Expected visit count 1, got %d", retrieved.VisitCount) @@ -88,19 +88,19 @@ func TestLocationVisitCount(t *testing.T) { func TestLocationVerification(t *testing.T) { creator := std.Address("g1test123") verifier := std.Address("g1verifier123") - + location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") - + if location.Verified { t.Error("Location should not be verified initially") } - + // Verify location success := locationManager.VerifyLocation(location.ID, verifier) if !success { t.Error("Failed to verify location") } - + retrieved := locationManager.GetLocation(location.ID) if !retrieved.Verified { t.Error("Location should be verified") diff --git a/packages/r/karma1337/geo-resto/qr_verification_test.gno b/packages/r/karma1337/geo-resto/qr_verification_test.gno index afb0305..feb97f7 100644 --- a/packages/r/karma1337/geo-resto/qr_verification_test.gno +++ b/packages/r/karma1337/geo-resto/qr_verification_test.gno @@ -9,122 +9,124 @@ import ( func TestQRCodeVerification(t *testing.T) { // Setup - use global managers to ensure they work together creator := std.Address("g1creator123") - + // Create a location first using global manager location := locationManager.AddLocation(creator, "Test Venue", "A test venue for events", 40.7128, -74.0060, "venue") if location == nil { t.Fatal("Failed to create location") } - + // Create an event using global manager currentTime := time.Now().Unix() - startTime := currentTime + 60 // Starts in 1 minute (close enough to current time) - endTime := currentTime + 7200 // Ends in 2 hours - + startTime := currentTime + 60 // Starts in 1 minute (close enough to current time) + endTime := currentTime + 7200 // Ends in 2 hours + event := eventManager.CreateEvent(creator, location.ID, "QR Test Event", "Testing QR verification", "", int(EventTypeMeetup), startTime, endTime) if event == nil { t.Fatal("Failed to create event") } - + // Test QR code generation if event.QRCode == "" { t.Error("QR code should be generated automatically") } - + if event.VerificationSecret == "" { t.Error("Verification secret should be generated automatically") } - + // Test verification code generation verificationCode := eventManager.GenerateAttendeeVerificationCode(event.ID, creator) if verificationCode == "" { t.Error("Should generate verification code for event creator") } - + if len(verificationCode) != 12 { t.Errorf("Verification code should be 12 characters, got %d", len(verificationCode)) } - + // Test attendee verification attendeeAddress := "g1attendee123" success := eventManager.VerifyAttendeePresence(event.ID, verificationCode, creator, attendeeAddress) if !success { t.Error("Should successfully verify attendee with valid code") } - + // Check that attendee is now in verified list verifiedAttendees := eventManager.GetVerifiedAttendees(event.ID, creator) if len(verifiedAttendees) != 1 { t.Errorf("Should have 1 verified attendee, got %d", len(verifiedAttendees)) } - + if verifiedAttendees[0] != attendeeAddress { t.Errorf("Expected attendee %s, got %s", attendeeAddress, verifiedAttendees[0]) } - + // Test duplicate verification (should not add twice) success2 := eventManager.VerifyAttendeePresence(event.ID, verificationCode, creator, attendeeAddress) if !success2 { t.Error("Should still return true for already verified attendee") } - + verifiedAttendees2 := eventManager.GetVerifiedAttendees(event.ID, creator) if len(verifiedAttendees2) != 1 { t.Error("Should not add duplicate verified attendee") } - + // Test invalid verification code invalidCode := "invalid12345" success3 := eventManager.VerifyAttendeePresence(event.ID, invalidCode, creator, "g1other123") if success3 { t.Error("Should not verify with invalid code") } - + // Test access control - non-creator shouldn't access QR code nonCreator := std.Address("g1other123") qrCode := eventManager.GetEventQRCode(event.ID, nonCreator) if qrCode != "" { t.Error("Non-creator should not access QR code") } - + // Test verification statistics total, verified, _ := eventManager.GetAttendeeVerificationStats(event.ID, creator) if total != 0 || verified != 1 { t.Errorf("Expected stats: total=0, verified=1, got total=%d, verified=%d", total, verified) } - + // Add a participant manually (since event might not be active in test) // This simulates what would happen if JoinEvent succeeded event.Participants = append(event.Participants, attendeeAddress) - total2, verified2, rate2 := eventManager.GetAttendeeVerificationStats(event.ID, creator) - if total2 != 1 || verified2 != 1 || rate2 != 100.0 { - t.Errorf("Expected stats: total=1, verified=1, rate=100.0, got total=%d, verified=%d, rate=%f", total2, verified2, rate2) + eventManager.events[event.ID] = event // Save changes + + total2, verified2, _ := eventManager.GetAttendeeVerificationStats(event.ID, creator) + if total2 != 1 || verified2 != 1 { + t.Errorf("Expected stats: total=1, verified=1, got total=%d, verified=%d", total2, verified2) } } func TestQRCodeTimeSecurity(t *testing.T) { // This test verifies that verification codes are time-limited // Note: In a real test environment, we'd manipulate time, but this shows the concept - + creator := std.Address("g1creator123") - + // Create location and event using global managers location := locationManager.AddLocation(creator, "Time Test Venue", "Testing time security", 40.7128, -74.0060, "venue") startTime := time.Now().Unix() + 3600 endTime := startTime + 7200 - + event := eventManager.CreateEvent(creator, location.ID, "Time Security Test", "Testing time-based security", "", int(EventTypeMeetup), startTime, endTime) - + // Generate a verification code code1 := eventManager.GenerateAttendeeVerificationCode(event.ID, creator) - + // Wait a moment and generate another code (should be different due to timestamp) // Note: In testing environment, time might not advance enough to see difference code2 := eventManager.GenerateAttendeeVerificationCode(event.ID, creator) - + // Codes might be the same in testing environment due to same timestamp // But the logic ensures they would be different in real-time usage - + if code1 == "" || code2 == "" { t.Error("Both verification codes should be generated") } diff --git a/packages/r/karma1337/geo-resto/renderer.gno b/packages/r/karma1337/geo-resto/renderer.gno index ca3d606..ec54d05 100644 --- a/packages/r/karma1337/geo-resto/renderer.gno +++ b/packages/r/karma1337/geo-resto/renderer.gno @@ -17,26 +17,26 @@ func NewRenderer() *Renderer { // RenderMainPage displays the main interface func (r *Renderer) RenderMainPage() string { var sb strings.Builder - + sb.WriteString("# 🌍 Geo-Resto - Decentralized Geographic Data\n\n") sb.WriteString("Welcome to Geo-Resto, a blockchain-based platform for sharing and verifying geographic data.\n\n") - + // Statistics allLocations := locationManager.GetAllLocations() activeEvents := eventManager.GetActiveEvents() - + sb.WriteString("## πŸ“Š Platform Statistics\n") sb.WriteString("- **Total Locations**: " + strconv.Itoa(len(allLocations)) + "\n") sb.WriteString("- **Active Events**: " + strconv.Itoa(len(activeEvents)) + "\n") sb.WriteString("- **Total Visits**: " + r.getTotalVisitCount() + "\n\n") - + // Navigation sb.WriteString("## πŸ—ΊοΈ Navigation\n") sb.WriteString("- [View All Locations](/r/karma1337/georesto:locations)\n") sb.WriteString("- [Recent Visits](/r/karma1337/georesto:visits)\n") sb.WriteString("- [Active Events](/r/karma1337/georesto:events)\n") sb.WriteString("- [World Map](/r/karma1337/georesto:map)\n\n") - + // Recent activity sb.WriteString("## πŸ”₯ Recent Activity\n") recentVisits := visitManager.GetRecentVisits(5) @@ -50,22 +50,22 @@ func (r *Renderer) RenderMainPage() string { } } } - + return sb.String() } // RenderAllLocations displays all locations in the system func (r *Renderer) RenderAllLocations() string { var sb strings.Builder - + sb.WriteString("# πŸ“ All Locations\n\n") - + locations := locationManager.GetAllLocations() if len(locations) == 0 { sb.WriteString("No locations have been added yet.\n") return sb.String() } - + // Group by category categories := make(map[string][]*Location) for _, location := range locations { @@ -75,7 +75,7 @@ func (r *Renderer) RenderAllLocations() string { } categories[category] = append(categories[category], location) } - + for category, categoryLocations := range categories { sb.WriteString("## " + category + "\n\n") for _, location := range categoryLocations { @@ -83,21 +83,21 @@ func (r *Renderer) RenderAllLocations() string { } sb.WriteString("\n") } - + return sb.String() } // RenderLocation displays details for a specific location func (r *Renderer) RenderLocation(locationID string) string { var sb strings.Builder - + location := locationManager.GetLocation(locationID) if location == nil { return "Location not found." } - + sb.WriteString("# πŸ“ " + location.Name + "\n\n") - + // Location details sb.WriteString("**Description**: " + location.Description + "\n") sb.WriteString("**Category**: " + location.Category + "\n") @@ -111,11 +111,11 @@ func (r *Renderer) RenderLocation(locationID string) string { sb.WriteString("**Status**: ⏳ Pending verification\n") } sb.WriteString("\n") - + // Recent visits visits := visitManager.GetLocationVisits(locationID) sb.WriteString("## πŸ”„ Recent Visits (" + strconv.Itoa(len(visits)) + ")\n\n") - + if len(visits) == 0 { sb.WriteString("No visits recorded yet.\n") } else { @@ -124,7 +124,7 @@ func (r *Renderer) RenderLocation(locationID string) string { if limit > 10 { limit = 10 } - + for i := len(visits) - limit; i < len(visits); i++ { visit := visits[i] sb.WriteString("- " + r.truncateAddress(visit.UserAddress) + " - " + r.formatTimestamp(visit.Timestamp)) @@ -134,7 +134,7 @@ func (r *Renderer) RenderLocation(locationID string) string { sb.WriteString("\n") } } - + // Associated events events := eventManager.GetLocationEvents(locationID) if len(events) > 0 { @@ -143,24 +143,24 @@ func (r *Renderer) RenderLocation(locationID string) string { sb.WriteString(r.renderEventCard(event)) } } - + return sb.String() } // RenderUserVisits displays visits for a specific user func (r *Renderer) RenderUserVisits(userAddress string) string { var sb strings.Builder - + sb.WriteString("# πŸ‘€ User Visits: " + r.truncateAddress(userAddress) + "\n\n") - + visits := visitManager.GetUserVisits(userAddress) if len(visits) == 0 { sb.WriteString("No visits recorded for this user.\n") return sb.String() } - + sb.WriteString("**Total Visits**: " + strconv.Itoa(len(visits)) + "\n\n") - + for _, visit := range visits { location := locationManager.GetLocation(visit.LocationID) if location != nil { @@ -178,22 +178,22 @@ func (r *Renderer) RenderUserVisits(userAddress string) string { sb.WriteString("\n") } } - + return sb.String() } // RenderRecentVisits displays recent visits across all users func (r *Renderer) RenderRecentVisits() string { var sb strings.Builder - + sb.WriteString("# πŸ”„ Recent Visits\n\n") - + visits := visitManager.GetRecentVisits(20) if len(visits) == 0 { sb.WriteString("No visits recorded yet.\n") return sb.String() } - + for _, visit := range visits { location := locationManager.GetLocation(visit.LocationID) if location != nil { @@ -204,45 +204,45 @@ func (r *Renderer) RenderRecentVisits() string { sb.WriteString("\n") } } - + return sb.String() } // RenderActiveEvents displays all active events func (r *Renderer) RenderActiveEvents() string { var sb strings.Builder - + sb.WriteString("# 🎯 Active Events\n\n") - + events := eventManager.GetActiveEvents() if len(events) == 0 { sb.WriteString("No active events at the moment.\n") return sb.String() } - + for _, event := range events { sb.WriteString(r.renderEventCard(event)) } - + return sb.String() } // RenderEvent displays details for a specific event func (r *Renderer) RenderEvent(eventID string) string { var sb strings.Builder - + event := eventManager.GetEvent(eventID) if event == nil { return "Event not found." } - + sb.WriteString("# 🎯 " + event.Name + "\n\n") - + // Event details sb.WriteString("**Description**: " + event.Description + "\n") sb.WriteString("**Type**: " + eventManager.GetEventTypeString(event.EventType) + "\n") sb.WriteString("**Location**: ") - + location := locationManager.GetLocation(event.LocationID) if location != nil { sb.WriteString("[" + location.Name + "](/r/karma1337/geo-resto:locations/" + event.LocationID + ")") @@ -250,7 +250,7 @@ func (r *Renderer) RenderEvent(eventID string) string { sb.WriteString(event.LocationID) } sb.WriteString("\n") - + sb.WriteString("**Created by**: " + r.truncateAddress(event.Creator.String()) + "\n") sb.WriteString("**Start Time**: " + r.formatTimestamp(event.StartTime) + "\n") sb.WriteString("**End Time**: " + r.formatTimestamp(event.EndTime) + "\n") @@ -259,27 +259,27 @@ func (r *Renderer) RenderEvent(eventID string) string { sb.WriteString(" / " + strconv.Itoa(event.MaxParticipants)) } sb.WriteString("\n") - + if event.IsActive { sb.WriteString("**Status**: 🟒 Active\n") } else { sb.WriteString("**Status**: πŸ”΄ Inactive\n") } - + if event.Rewards != "" { sb.WriteString("**Rewards**: " + event.Rewards + "\n") } - + if event.Password != "" { sb.WriteString("**Access**: πŸ”’ Password required\n") } - + sb.WriteString("\n") - + // QR Code and Verification Info (for event organizers) sb.WriteString("## πŸ“± Event Verification\n\n") sb.WriteString("**QR Code**: `" + event.QRCode + "`\n\n") - + // Verification Statistics totalParticipants := len(event.Participants) verifiedCount := len(event.VerifiedAttendees) @@ -288,7 +288,7 @@ func (r *Renderer) RenderEvent(eventID string) string { rateStr := strconv.Itoa(int(verificationRate)) + "." + strconv.Itoa(int((verificationRate-float64(int(verificationRate)))*10)) sb.WriteString("**Verification Stats**: " + strconv.Itoa(verifiedCount) + "/" + strconv.Itoa(totalParticipants) + " verified (" + rateStr + "%)\n\n") } - + // Verified Attendees List if len(event.VerifiedAttendees) > 0 { sb.WriteString("### βœ… Verified Attendees\n\n") @@ -297,7 +297,7 @@ func (r *Renderer) RenderEvent(eventID string) string { } sb.WriteString("\n") } - + sb.WriteString("\n") // Participants list @@ -307,30 +307,30 @@ func (r *Renderer) RenderEvent(eventID string) string { sb.WriteString(strconv.Itoa(i+1) + ". " + r.truncateAddress(participant) + "\n") } } - + return sb.String() } // RenderWorldMap displays a text-based world map representation func (r *Renderer) RenderWorldMap() string { var sb strings.Builder - + sb.WriteString("# πŸ—ΊοΈ World Map\n\n") sb.WriteString("*Text-based representation of location distribution*\n\n") - + locations := locationManager.GetAllLocations() if len(locations) == 0 { sb.WriteString("No locations to display on the map.\n") return sb.String() } - + // Group locations by region (simplified) regions := make(map[string][]*Location) for _, location := range locations { region := r.getRegionFromCoordinates(location.Latitude, location.Longitude) regions[region] = append(regions[region], location) } - + sb.WriteString("## 🌍 Locations by Region\n\n") for region, regionLocations := range regions { sb.WriteString("### " + region + " (" + strconv.Itoa(len(regionLocations)) + " locations)\n") @@ -339,7 +339,7 @@ func (r *Renderer) RenderWorldMap() string { } sb.WriteString("\n") } - + return sb.String() } @@ -347,7 +347,7 @@ func (r *Renderer) RenderWorldMap() string { func (r *Renderer) renderLocationCard(location *Location) string { var sb strings.Builder - + sb.WriteString("### [" + location.Name + "](/r/karma1337/geo-resto:locations/" + location.ID + ")\n") sb.WriteString(location.Description + "\n\n") sb.WriteString("πŸ“ *" + strconv.FormatFloat(location.Latitude, 'f', 4, 64) + ", " + strconv.FormatFloat(location.Longitude, 'f', 4, 64) + "*") @@ -356,13 +356,13 @@ func (r *Renderer) renderLocationCard(location *Location) string { sb.WriteString(" | βœ… Verified") } sb.WriteString("\n\n") - + return sb.String() } func (r *Renderer) renderEventCard(event *Event) string { var sb strings.Builder - + sb.WriteString("### [" + event.Name + "](/r/karma1337/geo-resto:events/" + event.ID + ")\n") sb.WriteString(event.Description + "\n\n") sb.WriteString("πŸ“… " + r.formatTimestamp(event.StartTime) + " - " + r.formatTimestamp(event.EndTime) + "\n") @@ -371,7 +371,7 @@ func (r *Renderer) renderEventCard(event *Event) string { sb.WriteString(" | 🟒 Active") } sb.WriteString("\n\n") - + return sb.String() } @@ -390,7 +390,7 @@ func (r *Renderer) formatTimestamp(timestamp int64) string { func (r *Renderer) timeAgo(timestamp int64) string { now := time.Now().Unix() diff := now - timestamp - + if diff < 60 { return "just now" } else if diff < 3600 { diff --git a/packages/r/karma1337/geo-resto/visit.gno b/packages/r/karma1337/geo-resto/visit.gno index 83c6730..b0dfd2c 100644 --- a/packages/r/karma1337/geo-resto/visit.gno +++ b/packages/r/karma1337/geo-resto/visit.gno @@ -1,31 +1,31 @@ package georesto import ( - "std" "crypto/sha256" "encoding/hex" + "std" "strconv" "time" ) // Visit represents a user's check-in at a location type Visit struct { - ID string // Unique visit identifier - UserAddress string // Address of the user who visited - LocationID string // ID of the visited location - Timestamp int64 // Unix timestamp of the visit - Proof string // Cryptographic proof or verification data - ProofHash string // Hash of the proof for integrity - Verified bool // Whether the visit has been verified - Notes string // Optional notes about the visit + ID string // Unique visit identifier + UserAddress string // Address of the user who visited + LocationID string // ID of the visited location + Timestamp int64 // Unix timestamp of the visit + Proof string // Cryptographic proof or verification data + ProofHash string // Hash of the proof for integrity + Verified bool // Whether the visit has been verified + Notes string // Optional notes about the visit } // VisitManager handles all visit and check-in related operations type VisitManager struct { - visits map[string]*Visit // Visit ID -> Visit - visitsByUser map[string][]string // User address -> Visit IDs + visits map[string]*Visit // Visit ID -> Visit + visitsByUser map[string][]string // User address -> Visit IDs visitsByLocation map[string][]string // Location ID -> Visit IDs - nextID int + nextID int } // NewVisitManager creates a new visit manager instance @@ -45,22 +45,22 @@ func (vm *VisitManager) CheckIn(user std.Address, locationID, proof string) *Vis if location == nil { return nil } - + userAddr := user.String() currentTime := time.Now().Unix() - + // Check for duplicate recent check-ins (prevent spam) if vm.hasRecentCheckIn(userAddr, locationID, currentTime, 3600) { // 1 hour cooldown return nil } - + // Generate unique visit ID visitID := "visit_" + strconv.Itoa(vm.nextID) vm.nextID++ - + // Create proof hash for integrity proofHash := vm.generateProofHash(userAddr, locationID, proof, currentTime) - + visit := &Visit{ ID: visitID, UserAddress: userAddr, @@ -71,19 +71,19 @@ func (vm *VisitManager) CheckIn(user std.Address, locationID, proof string) *Vis Verified: vm.verifyProof(proof, userAddr, locationID), Notes: "", } - + // Store the visit vm.visits[visitID] = visit - + // Index by user vm.visitsByUser[userAddr] = append(vm.visitsByUser[userAddr], visitID) - + // Index by location vm.visitsByLocation[locationID] = append(vm.visitsByLocation[locationID], visitID) - + // Update location visit count locationManager.IncrementVisitCount(locationID) - + return visit } @@ -102,7 +102,7 @@ func (vm *VisitManager) GetUserVisits(userAddress string) []*Visit { if !exists { return []*Visit{} } - + visits := make([]*Visit, 0, len(visitIDs)) for _, id := range visitIDs { if visit := vm.GetVisit(id); visit != nil { @@ -118,7 +118,7 @@ func (vm *VisitManager) GetLocationVisits(locationID string) []*Visit { if !exists { return []*Visit{} } - + visits := make([]*Visit, 0, len(visitIDs)) for _, id := range visitIDs { if visit := vm.GetVisit(id); visit != nil { @@ -143,7 +143,7 @@ func (vm *VisitManager) GetRecentVisits(limit int) []*Visit { for _, visit := range vm.visits { allVisits = append(allVisits, visit) } - + // Use a more efficient sorting algorithm (insertion sort) for i := 1; i < len(allVisits); i++ { j := i @@ -152,7 +152,7 @@ func (vm *VisitManager) GetRecentVisits(limit int) []*Visit { j-- } } - + if limit > 0 && limit < len(allVisits) { return allVisits[:limit] } @@ -162,7 +162,7 @@ func (vm *VisitManager) GetRecentVisits(limit int) []*Visit { // VerifyVisit provides cryptographic verification that a visit occurred func (vm *VisitManager) VerifyVisit(userAddress, locationID string, timestamp int64) bool { userVisits := vm.GetUserVisits(userAddress) - + for _, visit := range userVisits { if visit.LocationID == locationID && visit.Timestamp == timestamp { return visit.Verified @@ -174,7 +174,7 @@ func (vm *VisitManager) VerifyVisit(userAddress, locationID string, timestamp in // hasRecentCheckIn checks if user has checked in recently to prevent spam func (vm *VisitManager) hasRecentCheckIn(userAddress, locationID string, currentTime int64, cooldownSeconds int64) bool { userVisits := vm.GetUserVisits(userAddress) - + for _, visit := range userVisits { if visit.LocationID == locationID { timeDiff := currentTime - visit.Timestamp @@ -200,7 +200,7 @@ func (vm *VisitManager) verifyProof(proof, userAddress, locationID string) bool // 2. QR code validation // 3. Time-based tokens // 4. Cryptographic signatures - + // We now use the challenge-response mechanism from authManager return authManager.VerifyLocationChallenge(locationID, proof) } @@ -211,7 +211,7 @@ func (vm *VisitManager) AddVisitNotes(visitID, notes string, user std.Address) b if visit == nil || visit.UserAddress != user.String() { return false } - + visit.Notes = notes return true } diff --git a/packages/r/karma1337/geo-resto/visit_test.gno b/packages/r/karma1337/geo-resto/visit_test.gno index 2f3c1b7..67cb5f9 100644 --- a/packages/r/karma1337/geo-resto/visit_test.gno +++ b/packages/r/karma1337/geo-resto/visit_test.gno @@ -8,38 +8,38 @@ import ( func TestVisitManager(t *testing.T) { // Setup - use global managers creator := std.Address("g1creator123") - + // Create a test location using global manager location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") - + // Test check-in user := std.Address("g1user123") visit := visitManager.CheckIn(user, location.ID, "test-proof") - + if visit == nil { t.Fatal("Failed to check in") } - + if visit.UserAddress != user.String() { t.Errorf("Expected user address %s, got %s", user.String(), visit.UserAddress) } - + if visit.LocationID != location.ID { t.Errorf("Expected location ID %s, got %s", location.ID, visit.LocationID) } - + // Test retrieving visit retrieved := visitManager.GetVisit(visit.ID) if retrieved == nil { t.Fatal("Failed to retrieve visit") } - + // Test user visits userVisits := visitManager.GetUserVisits(user.String()) if len(userVisits) != 1 { t.Errorf("Expected 1 visit for user, got %d", len(userVisits)) } - + // Test location visits locationVisits := visitManager.GetLocationVisits(location.ID) if len(locationVisits) != 1 { @@ -48,18 +48,18 @@ func TestVisitManager(t *testing.T) { } func TestVisitCooldown(t *testing.T) { - + creator := std.Address("g1creator123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") - + user := std.Address("g1user123") - + // First check-in should succeed visit1 := visitManager.CheckIn(user, location.ID, "test-proof-1") if visit1 == nil { t.Fatal("First check-in should succeed") } - + // Immediate second check-in should fail due to cooldown visit2 := visitManager.CheckIn(user, location.ID, "test-proof-2") if visit2 != nil { @@ -68,30 +68,29 @@ func TestVisitCooldown(t *testing.T) { } func TestVisitVerification(t *testing.T) { - + creator := std.Address("g1creator123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") - + user := std.Address("g1user123") - + // Generate a valid proof using the challenge-response system validProof := authManager.GenerateLocationChallenge(location.ID) visit := visitManager.CheckIn(user, location.ID, validProof) - + if visit == nil { t.Fatal("Failed to check in") } - + if !visit.Verified { t.Fatal("Visit should be marked as verified with a valid proof") } - // Test verification verified := visitManager.VerifyVisit(user.String(), location.ID, visit.Timestamp) if !verified { t.Error("Visit should be verified") } - + // Test non-existent visit notVerified := visitManager.VerifyVisit(user.String(), location.ID, visit.Timestamp+1000) if notVerified { @@ -100,29 +99,29 @@ func TestVisitVerification(t *testing.T) { } func TestRecentVisits(t *testing.T) { - + creator := std.Address("g1creator123") location1 := locationManager.AddLocation(creator, "Location 1", "Test", 48.8566, 2.3522, "test") location2 := locationManager.AddLocation(creator, "Location 2", "Test", 48.8600, 2.3500, "test") - + user1 := std.Address("g1user1") user2 := std.Address("g1user2") - + // Create visits with slight time differences visit1 := visitManager.CheckIn(user1, location1.ID, "proof1") // Note: In a real environment, visits would have different timestamps visit2 := visitManager.CheckIn(user2, location2.ID, "proof2") - + if visit1 == nil || visit2 == nil { t.Fatal("Failed to create visits") } - + // Test getting recent visits recentVisits := visitManager.GetRecentVisits(5) if len(recentVisits) < 2 { t.Errorf("Expected at least 2 recent visits, got %d", len(recentVisits)) } - + // Check that visits are sorted by timestamp (newest first) if recentVisits[0].Timestamp < recentVisits[1].Timestamp { t.Error("Recent visits should be sorted by timestamp (newest first)") @@ -130,29 +129,29 @@ func TestRecentVisits(t *testing.T) { } func TestVisitNotes(t *testing.T) { - + creator := std.Address("g1creator123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") - + user := std.Address("g1user123") visit := visitManager.CheckIn(user, location.ID, "test-proof") - + if visit == nil { t.Fatal("Failed to check in") } - + // Add notes success := visitManager.AddVisitNotes(visit.ID, "Great place!", user) if !success { t.Error("Failed to add visit notes") } - + // Verify notes were added retrieved := visitManager.GetVisit(visit.ID) if retrieved.Notes != "Great place!" { t.Errorf("Expected notes 'Great place!', got '%s'", retrieved.Notes) } - + // Test unauthorized note addition otherUser := std.Address("g1other123") unauthorized := visitManager.AddVisitNotes(visit.ID, "Unauthorized", otherUser) From 96aa0ce56ead108a8be83294cd9de413ee1fb44c Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Thu, 23 Oct 2025 15:00:01 +0200 Subject: [PATCH 04/25] feat: add new event types and update event participation method names feat: implement GetLocationsByCategory function in LocationManager fix: rename GetLocationsByUser to GetUserLocations in location tests --- packages/r/karma1337/geo-resto/event.gno | 7 +++++-- packages/r/karma1337/geo-resto/event_test.gno | 4 ++-- packages/r/karma1337/geo-resto/geo_resto.gno | 2 +- packages/r/karma1337/geo-resto/location.gno | 11 +++++++++++ packages/r/karma1337/geo-resto/location_test.gno | 2 +- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/r/karma1337/geo-resto/event.gno b/packages/r/karma1337/geo-resto/event.gno index dd94bf2..aca08d1 100644 --- a/packages/r/karma1337/geo-resto/event.gno +++ b/packages/r/karma1337/geo-resto/event.gno @@ -16,6 +16,9 @@ const ( EventTypeMeetup EventType = iota EventTypeAirdrop EventTypeQuest + EventTypeContest + EventTypeCheckpoint + EventTypeEmergency ) // Event represents a location-based event (airdrops, meetups, etc.) @@ -248,8 +251,8 @@ func (em *EventManager) SetMaxParticipants(eventID string, maxParticipants int, return true } -// IsParticipant checks if a user is a participant in an event -func (em *EventManager) IsParticipant(eventID, userAddress string) bool { +// IsUserParticipant checks if a user is a participant in an event +func (em *EventManager) IsUserParticipant(eventID, userAddress string) bool { event := em.GetEvent(eventID) if event == nil { return false diff --git a/packages/r/karma1337/geo-resto/event_test.gno b/packages/r/karma1337/geo-resto/event_test.gno index 3532963..b335f0b 100644 --- a/packages/r/karma1337/geo-resto/event_test.gno +++ b/packages/r/karma1337/geo-resto/event_test.gno @@ -69,13 +69,13 @@ func TestEventJoining(t *testing.T) { user := std.Address("g1user123") // Test joining with correct password - success := eventManager.JoinEvent(user, event.ID, "password123") + success := eventManager.JoinEvent(event.ID, "password123", user) if !success { t.Error("Should be able to join event with correct password") } // Test joining with incorrect password - success2 := eventManager.JoinEvent(user, event.ID, "wrongpassword") + success2 := eventManager.JoinEvent(event.ID, "wrongpassword", user) if success2 { t.Error("Should not be able to join event with incorrect password") } diff --git a/packages/r/karma1337/geo-resto/geo_resto.gno b/packages/r/karma1337/geo-resto/geo_resto.gno index fbe7341..876aa6d 100644 --- a/packages/r/karma1337/geo-resto/geo_resto.gno +++ b/packages/r/karma1337/geo-resto/geo_resto.gno @@ -90,7 +90,7 @@ func JoinEvent(eventID, password string) string { if !authManager.CheckRateLimit(caller.String(), "join_event:"+eventID, 60) { return "Rate limit exceeded. Please try again later." } - success := eventManager.JoinEvent(caller, eventID, password) + success := eventManager.JoinEvent(eventID, password, caller) if success { return "Successfully joined event: " + eventID } diff --git a/packages/r/karma1337/geo-resto/location.gno b/packages/r/karma1337/geo-resto/location.gno index 6b853de..96847b7 100644 --- a/packages/r/karma1337/geo-resto/location.gno +++ b/packages/r/karma1337/geo-resto/location.gno @@ -151,3 +151,14 @@ func (lm *LocationManager) GetNearbyLocations(latitude, longitude, radiusKm floa return nearby } + +// GetLocationsByCategory returns all locations in a specific category +func (lm *LocationManager) GetLocationsByCategory(category string) []*Location { + byCategory := make([]*Location, 0) + for _, location := range lm.locations { + if location.Category == category { + byCategory = append(byCategory, location) + } + } + return byCategory +} diff --git a/packages/r/karma1337/geo-resto/location_test.gno b/packages/r/karma1337/geo-resto/location_test.gno index c8e30ab..a9876e8 100644 --- a/packages/r/karma1337/geo-resto/location_test.gno +++ b/packages/r/karma1337/geo-resto/location_test.gno @@ -33,7 +33,7 @@ func TestLocationManager(t *testing.T) { } // Test getting locations by user - userLocations := locationManager.GetLocationsByUser(creator.String()) + userLocations := locationManager.GetUserLocations(creator.String()) if len(userLocations) != 1 { t.Errorf("Expected 1 location for user, got %d", len(userLocations)) } From a286517ce8ae4e4305e2b7e64cd53b15f6d4318b Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Thu, 23 Oct 2025 16:16:34 +0200 Subject: [PATCH 05/25] refactor: replace std.Address with address type across geo-resto package --- packages/r/karma1337/geo-resto/auth.gno | 13 ++-- packages/r/karma1337/geo-resto/auth_test.gno | 7 +-- packages/r/karma1337/geo-resto/event.gno | 59 ++++++++++--------- packages/r/karma1337/geo-resto/event_test.gno | 13 ++-- packages/r/karma1337/geo-resto/geo_resto.gno | 14 ++--- packages/r/karma1337/geo-resto/location.gno | 25 ++++---- .../r/karma1337/geo-resto/location_test.gno | 13 ++-- .../geo-resto/qr_verification_test.gno | 7 +-- packages/r/karma1337/geo-resto/visit.gno | 5 +- packages/r/karma1337/geo-resto/visit_test.gno | 25 ++++---- 10 files changed, 89 insertions(+), 92 deletions(-) diff --git a/packages/r/karma1337/geo-resto/auth.gno b/packages/r/karma1337/geo-resto/auth.gno index 87af0fa..44fd3e3 100644 --- a/packages/r/karma1337/geo-resto/auth.gno +++ b/packages/r/karma1337/geo-resto/auth.gno @@ -4,7 +4,6 @@ import ( "crypto/sha256" "encoding/hex" "math" - "std" "strconv" "time" ) @@ -28,7 +27,7 @@ func NewAuthManager() *AuthManager { } // AddTrustedVerifier adds a trusted verifier address -func (am *AuthManager) AddTrustedVerifier(verifierAddress string, admin std.Address) bool { +func (am *AuthManager) AddTrustedVerifier(verifierAddress string, admin address) bool { if !am.IsAdmin(admin.String()) { return false } @@ -38,7 +37,7 @@ func (am *AuthManager) AddTrustedVerifier(verifierAddress string, admin std.Addr } // RemoveTrustedVerifier removes a trusted verifier address -func (am *AuthManager) RemoveTrustedVerifier(verifierAddress string, admin std.Address) bool { +func (am *AuthManager) RemoveTrustedVerifier(verifierAddress string, admin address) bool { if !am.IsAdmin(admin.String()) { return false } @@ -53,7 +52,7 @@ func (am *AuthManager) IsTrustedVerifier(address string) bool { } // AddAdmin adds an admin address -func (am *AuthManager) AddAdmin(adminAddress string, currentAdmin std.Address) bool { +func (am *AuthManager) AddAdmin(adminAddress string, currentAdmin address) bool { // Only existing admins can add new admins if len(am.adminAddresses) > 0 && !am.IsAdmin(currentAdmin.String()) { return false @@ -81,7 +80,7 @@ func (am *AuthManager) GenerateLocationProof(userAddress, locationID string, tim } // VerifyEventAccess verifies access to a password-protected event -func (am *AuthManager) VerifyEventAccess(eventID, password string, user std.Address) bool { +func (am *AuthManager) VerifyEventAccess(eventID, password string, user address) bool { event := eventManager.GetEvent(eventID) if event == nil { return false @@ -98,7 +97,7 @@ func (am *AuthManager) VerifyEventAccess(eventID, password string, user std.Addr } // CanModifyLocation checks if a user can modify a location -func (am *AuthManager) CanModifyLocation(locationID string, user std.Address) bool { +func (am *AuthManager) CanModifyLocation(locationID string, user address) bool { location := locationManager.GetLocation(locationID) if location == nil { return false @@ -125,7 +124,7 @@ func (am *AuthManager) CanModifyLocation(locationID string, user std.Address) bo } // CanModifyEvent checks if a user can modify an event -func (am *AuthManager) CanModifyEvent(eventID string, user std.Address) bool { +func (am *AuthManager) CanModifyEvent(eventID string, user address) bool { event := eventManager.GetEvent(eventID) if event == nil { return false diff --git a/packages/r/karma1337/geo-resto/auth_test.gno b/packages/r/karma1337/geo-resto/auth_test.gno index 2a777b5..13c97e0 100644 --- a/packages/r/karma1337/geo-resto/auth_test.gno +++ b/packages/r/karma1337/geo-resto/auth_test.gno @@ -1,7 +1,6 @@ package georesto import ( - "std" "testing" "time" // time.Sleep is not available in Gno ) @@ -28,8 +27,8 @@ func TestRateLimiting(t *testing.T) { func TestAdminPermissions(t *testing.T) { auth := NewAuthManager() - admin := std.Address("g1admin") - user := std.Address("g1user") + admin := address("g1admin") + user := address("g1user") verifier := "g1verifier" // Initially, no admins @@ -38,7 +37,7 @@ func TestAdminPermissions(t *testing.T) { } // Add admin (first one doesn't require an existing admin) - auth.AddAdmin(admin.String(), std.Address("")) + auth.AddAdmin(admin.String(), address("")) if !auth.IsAdmin(admin.String()) { t.Fatal("Failed to add first admin") } diff --git a/packages/r/karma1337/geo-resto/event.gno b/packages/r/karma1337/geo-resto/event.gno index aca08d1..99fc56f 100644 --- a/packages/r/karma1337/geo-resto/event.gno +++ b/packages/r/karma1337/geo-resto/event.gno @@ -3,7 +3,6 @@ package georesto import ( "crypto/sha256" "encoding/hex" - "std" "strconv" "strings" "time" @@ -23,23 +22,23 @@ const ( // Event represents a location-based event (airdrops, meetups, etc.) type Event struct { - ID string // Unique event identifier - LocationID string // Associated location ID - Creator std.Address // Event creator - Name string // Event name - Description string // Event description - EventType EventType // Type of event - Password string // Access password (hashed) - StartTime int64 // Event start timestamp - EndTime int64 // Event end timestamp - CreatedAt int64 // Creation timestamp - Participants []string // List of participant addresses - MaxParticipants int // Maximum number of participants (0 = unlimited) - IsActive bool // Whether the event is currently active - Rewards string // Description of rewards/airdrops - QRCode string // QR code data for event verification - VerificationSecret string // Secret key for generating verification codes - VerifiedAttendees []string // List of verified attendee addresses + ID string // Unique event identifier + LocationID string // Associated location ID + Creator address // Event creator + Name string // Event name + Description string // Event description + EventType EventType // Type of event + Password string // Access password (hashed) + StartTime int64 // Event start timestamp + EndTime int64 // Event end timestamp + CreatedAt int64 // Creation timestamp + Participants []string // List of participant addresses + MaxParticipants int // Maximum number of participants (0 = unlimited) + IsActive bool // Whether the event is currently active + Rewards string // Description of rewards/airdrops + QRCode string // QR code data for event verification + VerificationSecret string // Secret key for generating verification codes + VerifiedAttendees []string // List of verified attendee addresses } // EventManager handles all event-related operations @@ -63,7 +62,7 @@ func NewEventManager() *EventManager { } // CreateEvent creates a new event -func (em *EventManager) CreateEvent(creator std.Address, locationID, name, description, password string, eventType int, startTime, endTime int64) *Event { +func (em *EventManager) CreateEvent(creator address, locationID, name, description, password string, eventType int, startTime, endTime int64) *Event { // Verify the location exists location := locationManager.GetLocation(locationID) if location == nil { @@ -137,7 +136,7 @@ func (em *EventManager) CreateEvent(creator std.Address, locationID, name, descr } // JoinEvent allows a user to join an event -func (em *EventManager) JoinEvent(eventID, password string, user std.Address) bool { +func (em *EventManager) JoinEvent(eventID, password string, user address) bool { event := em.GetEvent(eventID) if event == nil { return false @@ -230,7 +229,7 @@ func (em *EventManager) GetCreatorEvents(creatorAddress string) []*Event { } // SetEventRewards sets the rewards for an event -func (em *EventManager) SetEventRewards(eventID, rewards string, user std.Address) bool { +func (em *EventManager) SetEventRewards(eventID, rewards string, user address) bool { event := em.GetEvent(eventID) if event == nil || event.Creator.String() != user.String() { return false @@ -241,7 +240,7 @@ func (em *EventManager) SetEventRewards(eventID, rewards string, user std.Addres } // SetMaxParticipants sets the maximum number of participants for an event -func (em *EventManager) SetMaxParticipants(eventID string, maxParticipants int, user std.Address) bool { +func (em *EventManager) SetMaxParticipants(eventID string, maxParticipants int, user address) bool { event := em.GetEvent(eventID) if event == nil || event.Creator.String() != user.String() { return false @@ -314,6 +313,12 @@ func (em *EventManager) GetEventTypeString(eventType EventType) string { return "Airdrop" case EventTypeQuest: return "Quest" + case EventTypeContest: + return "Contest" + case EventTypeCheckpoint: + return "Checkpoint" + case EventTypeEmergency: + return "Emergency" default: return "Unknown" } @@ -335,7 +340,7 @@ func (em *EventManager) generateVerificationSecret(eventID, creatorAddress strin } // GenerateAttendeeVerificationCode generates a time-sensitive code for an attendee to prove presence -func (em *EventManager) GenerateAttendeeVerificationCode(eventID string, attendeeAddress std.Address) string { +func (em *EventManager) GenerateAttendeeVerificationCode(eventID string, attendeeAddress address) string { event := em.GetEvent(eventID) if event == nil { return "" @@ -354,7 +359,7 @@ func (em *EventManager) GenerateAttendeeVerificationCode(eventID string, attende } // VerifyAttendeePresence verifies an attendee's presence using a verification code -func (em *EventManager) VerifyAttendeePresence(eventID, verificationCode string, organizerAddress std.Address, attendeeAddress string) bool { +func (em *EventManager) VerifyAttendeePresence(eventID, verificationCode string, organizerAddress address, attendeeAddress string) bool { event := em.GetEvent(eventID) if event == nil { return false @@ -389,7 +394,7 @@ func (em *EventManager) VerifyAttendeePresence(eventID, verificationCode string, } // GetEventQRCode returns the QR code for an event (organizer only) -func (em *EventManager) GetEventQRCode(eventID string, requesterAddress std.Address) string { +func (em *EventManager) GetEventQRCode(eventID string, requesterAddress address) string { event := em.GetEvent(eventID) if event == nil { return "" @@ -404,7 +409,7 @@ func (em *EventManager) GetEventQRCode(eventID string, requesterAddress std.Addr } // GetVerifiedAttendees returns the list of verified attendees (organizer only) -func (em *EventManager) GetVerifiedAttendees(eventID string, requesterAddress std.Address) []string { +func (em *EventManager) GetVerifiedAttendees(eventID string, requesterAddress address) []string { event := em.GetEvent(eventID) if event == nil { return []string{} @@ -419,7 +424,7 @@ func (em *EventManager) GetVerifiedAttendees(eventID string, requesterAddress st } // GetAttendeeVerificationStats returns statistics on attendee verification (organizer only) -func (em *EventManager) GetAttendeeVerificationStats(eventID string, requesterAddress std.Address) (int, int, float64) { +func (em *EventManager) GetAttendeeVerificationStats(eventID string, requesterAddress address) (int, int, float64) { event := em.GetEvent(eventID) if event == nil { return 0, 0, 0.0 diff --git a/packages/r/karma1337/geo-resto/event_test.gno b/packages/r/karma1337/geo-resto/event_test.gno index b335f0b..b443606 100644 --- a/packages/r/karma1337/geo-resto/event_test.gno +++ b/packages/r/karma1337/geo-resto/event_test.gno @@ -1,14 +1,13 @@ package georesto import ( - "std" "testing" "time" ) func TestEventManager(t *testing.T) { // Setup - use global managers - creator := std.Address("g1creator123") + creator := address("g1creator123") // Create a test location using global manager location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") @@ -48,7 +47,7 @@ func TestEventManager(t *testing.T) { } func TestEventJoining(t *testing.T) { - creator := std.Address("g1creator123") + creator := address("g1creator123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") // Create an active event (starting now) @@ -66,7 +65,7 @@ func TestEventJoining(t *testing.T) { endTime, ) - user := std.Address("g1user123") + user := address("g1user123") // Test joining with correct password success := eventManager.JoinEvent(event.ID, "password123", user) @@ -88,7 +87,7 @@ func TestEventJoining(t *testing.T) { } func TestEventStatusUpdates(t *testing.T) { - creator := std.Address("g1creator123") + creator := address("g1creator123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") // Create a future event @@ -128,7 +127,7 @@ func TestEventStatusUpdates(t *testing.T) { func TestEventsByLocation(t *testing.T) { - creator := std.Address("g1creator123") + creator := address("g1creator123") location1 := locationManager.AddLocation(creator, "Location 1", "Test", 48.8566, 2.3522, "test") location2 := locationManager.AddLocation(creator, "Location 2", "Test", 48.8600, 2.3500, "test") @@ -185,7 +184,7 @@ func TestEventTypeString(t *testing.T) { func TestEventValidation(t *testing.T) { - creator := std.Address("g1creator123") + creator := address("g1creator123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") currentTime := time.Now().Unix() diff --git a/packages/r/karma1337/geo-resto/geo_resto.gno b/packages/r/karma1337/geo-resto/geo_resto.gno index 876aa6d..fb618e6 100644 --- a/packages/r/karma1337/geo-resto/geo_resto.gno +++ b/packages/r/karma1337/geo-resto/geo_resto.gno @@ -1,7 +1,7 @@ package georesto import ( - "std" + "chain/runtime" "strconv" "strings" ) @@ -53,7 +53,7 @@ func Render(path string) string { // AddLocation allows adding a new geographic location to the system func AddLocation(name, description string, latitude, longitude float64, category string) string { - caller := std.PreviousRealm().Address() + caller := runtime.PreviousRealm().Address() if !authManager.CheckRateLimit(caller.String(), "add_location", 60) { return "Rate limit exceeded. Please try again later." } @@ -63,7 +63,7 @@ func AddLocation(name, description string, latitude, longitude float64, category // CheckIn allows a user to check in at a specific location func CheckIn(locationID, proof string) string { - caller := std.PreviousRealm().Address() + caller := runtime.PreviousRealm().Address() if !authManager.CheckRateLimit(caller.String(), "check_in", 60) { return "Rate limit exceeded. Please try again later." } @@ -76,7 +76,7 @@ func CheckIn(locationID, proof string) string { // CreateEvent creates a location-based event (like airdrops) func CreateEvent(locationID, name, description, password string, eventType int, startTime, endTime int64) string { - caller := std.PreviousRealm().Address() + caller := runtime.PreviousRealm().Address() if !authManager.CheckRateLimit(caller.String(), "create_event", 300) { // 5 minute cooldown return "Rate limit exceeded. Please try again later." } @@ -86,7 +86,7 @@ func CreateEvent(locationID, name, description, password string, eventType int, // JoinEvent allows a user to join an event with proper authentication func JoinEvent(eventID, password string) string { - caller := std.PreviousRealm().Address() + caller := runtime.PreviousRealm().Address() if !authManager.CheckRateLimit(caller.String(), "join_event:"+eventID, 60) { return "Rate limit exceeded. Please try again later." } @@ -100,7 +100,7 @@ func JoinEvent(eventID, password string) string { // GetUserStats returns statistics for a specific user func GetUserStats(userAddress string) string { if userAddress == "" { - userAddress = std.PreviousRealm().Address().String() + userAddress = runtime.PreviousRealm().Address().String() } visitCount := visitManager.GetUserVisitCount(userAddress) @@ -115,7 +115,7 @@ func GetEventStats(eventID string) string { return "Error: Event ID is required" } - caller := std.PreviousRealm().Address() + caller := runtime.PreviousRealm().Address() total, verified, rate := eventManager.GetAttendeeVerificationStats(eventID, caller) rateStr := strconv.Itoa(int(rate)) + "." + strconv.Itoa(int((rate-float64(int(rate)))*10)) return "Event " + eventID + " - Total Participants: " + strconv.Itoa(total) + diff --git a/packages/r/karma1337/geo-resto/location.gno b/packages/r/karma1337/geo-resto/location.gno index 96847b7..39413f6 100644 --- a/packages/r/karma1337/geo-resto/location.gno +++ b/packages/r/karma1337/geo-resto/location.gno @@ -1,7 +1,6 @@ package georesto import ( - "std" "strconv" "strings" "time" @@ -9,16 +8,16 @@ import ( // Location represents a geographic point with metadata type Location struct { - ID string // Unique identifier - Name string // Human-readable name - Description string // Detailed description - Latitude float64 // GPS latitude - Longitude float64 // GPS longitude - Category string // Type of location (restaurant, landmark, etc.) - Creator std.Address // Address of the user who added this location - CreatedAt int64 // Unix timestamp - VisitCount int // Number of check-ins at this location - Verified bool // Whether location is verified by community + ID string // Unique identifier + Name string // Human-readable name + Description string // Detailed description + Latitude float64 // GPS latitude + Longitude float64 // GPS longitude + Category string // Type of location (restaurant, landmark, etc.) + Creator address // Address of the user who added this location + CreatedAt int64 // Unix timestamp + VisitCount int // Number of check-ins at this location + Verified bool // Whether location is verified by community } // LocationManager handles all location-related operations @@ -38,7 +37,7 @@ func NewLocationManager() *LocationManager { } // AddLocation adds a new location to the system -func (lm *LocationManager) AddLocation(creator std.Address, name, description string, latitude, longitude float64, category string) *Location { +func (lm *LocationManager) AddLocation(creator address, name, description string, latitude, longitude float64, category string) *Location { // Validate coordinates if latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180 { panic("Invalid coordinates") @@ -126,7 +125,7 @@ func (lm *LocationManager) IncrementVisitCount(id string) { } // VerifyLocation marks a location as verified -func (lm *LocationManager) VerifyLocation(id string, verifier std.Address) bool { +func (lm *LocationManager) VerifyLocation(id string, verifier address) bool { location := lm.GetLocation(id) if location == nil { return false diff --git a/packages/r/karma1337/geo-resto/location_test.gno b/packages/r/karma1337/geo-resto/location_test.gno index a9876e8..4a4806c 100644 --- a/packages/r/karma1337/geo-resto/location_test.gno +++ b/packages/r/karma1337/geo-resto/location_test.gno @@ -1,13 +1,12 @@ package georesto import ( - "std" "testing" ) func TestLocationManager(t *testing.T) { // Test adding a location using global manager - creator := std.Address("g1test123") + creator := address("g1test123") location := locationManager.AddLocation(creator, "Test Restaurant", "A great place to eat", 48.8566, 2.3522, "restaurant") if location == nil { @@ -48,7 +47,7 @@ func TestLocationManager(t *testing.T) { } func TestLocationCategories(t *testing.T) { - creator := std.Address("g1test123") + creator := address("g1test123") // Add locations in different categories locationManager.AddLocation(creator, "Restaurant 1", "Food", 48.8566, 2.3522, "restaurant") @@ -68,7 +67,7 @@ func TestLocationCategories(t *testing.T) { } func TestLocationVisitCount(t *testing.T) { - creator := std.Address("g1test123") + creator := address("g1test123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") @@ -86,8 +85,8 @@ func TestLocationVisitCount(t *testing.T) { } func TestLocationVerification(t *testing.T) { - creator := std.Address("g1test123") - verifier := std.Address("g1verifier123") + creator := address("g1test123") + verifier := address("g1verifier123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") @@ -109,7 +108,7 @@ func TestLocationVerification(t *testing.T) { func TestNearbyLocations(t *testing.T) { lm := NewLocationManager() - creator := std.Address("g1creator") + creator := address("g1creator") // Paris lm.AddLocation(creator, "Eiffel Tower", "", 48.8584, 2.2945, "landmark") diff --git a/packages/r/karma1337/geo-resto/qr_verification_test.gno b/packages/r/karma1337/geo-resto/qr_verification_test.gno index feb97f7..759a298 100644 --- a/packages/r/karma1337/geo-resto/qr_verification_test.gno +++ b/packages/r/karma1337/geo-resto/qr_verification_test.gno @@ -1,14 +1,13 @@ package georesto import ( - "std" "testing" "time" ) func TestQRCodeVerification(t *testing.T) { // Setup - use global managers to ensure they work together - creator := std.Address("g1creator123") + creator := address("g1creator123") // Create a location first using global manager location := locationManager.AddLocation(creator, "Test Venue", "A test venue for events", 40.7128, -74.0060, "venue") @@ -81,7 +80,7 @@ func TestQRCodeVerification(t *testing.T) { } // Test access control - non-creator shouldn't access QR code - nonCreator := std.Address("g1other123") + nonCreator := address("g1other123") qrCode := eventManager.GetEventQRCode(event.ID, nonCreator) if qrCode != "" { t.Error("Non-creator should not access QR code") @@ -108,7 +107,7 @@ func TestQRCodeTimeSecurity(t *testing.T) { // This test verifies that verification codes are time-limited // Note: In a real test environment, we'd manipulate time, but this shows the concept - creator := std.Address("g1creator123") + creator := address("g1creator123") // Create location and event using global managers location := locationManager.AddLocation(creator, "Time Test Venue", "Testing time security", 40.7128, -74.0060, "venue") diff --git a/packages/r/karma1337/geo-resto/visit.gno b/packages/r/karma1337/geo-resto/visit.gno index b0dfd2c..59b69e3 100644 --- a/packages/r/karma1337/geo-resto/visit.gno +++ b/packages/r/karma1337/geo-resto/visit.gno @@ -3,7 +3,6 @@ package georesto import ( "crypto/sha256" "encoding/hex" - "std" "strconv" "time" ) @@ -39,7 +38,7 @@ func NewVisitManager() *VisitManager { } // CheckIn records a user's visit to a location -func (vm *VisitManager) CheckIn(user std.Address, locationID, proof string) *Visit { +func (vm *VisitManager) CheckIn(user address, locationID, proof string) *Visit { // Verify the location exists location := locationManager.GetLocation(locationID) if location == nil { @@ -206,7 +205,7 @@ func (vm *VisitManager) verifyProof(proof, userAddress, locationID string) bool } // AddVisitNotes allows adding notes to an existing visit -func (vm *VisitManager) AddVisitNotes(visitID, notes string, user std.Address) bool { +func (vm *VisitManager) AddVisitNotes(visitID, notes string, user address) bool { visit := vm.GetVisit(visitID) if visit == nil || visit.UserAddress != user.String() { return false diff --git a/packages/r/karma1337/geo-resto/visit_test.gno b/packages/r/karma1337/geo-resto/visit_test.gno index 67cb5f9..f534864 100644 --- a/packages/r/karma1337/geo-resto/visit_test.gno +++ b/packages/r/karma1337/geo-resto/visit_test.gno @@ -1,19 +1,18 @@ package georesto import ( - "std" "testing" ) func TestVisitManager(t *testing.T) { // Setup - use global managers - creator := std.Address("g1creator123") + creator := address("g1creator123") // Create a test location using global manager location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") // Test check-in - user := std.Address("g1user123") + user := address("g1user123") visit := visitManager.CheckIn(user, location.ID, "test-proof") if visit == nil { @@ -49,10 +48,10 @@ func TestVisitManager(t *testing.T) { func TestVisitCooldown(t *testing.T) { - creator := std.Address("g1creator123") + creator := address("g1creator123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") - user := std.Address("g1user123") + user := address("g1user123") // First check-in should succeed visit1 := visitManager.CheckIn(user, location.ID, "test-proof-1") @@ -69,10 +68,10 @@ func TestVisitCooldown(t *testing.T) { func TestVisitVerification(t *testing.T) { - creator := std.Address("g1creator123") + creator := address("g1creator123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") - user := std.Address("g1user123") + user := address("g1user123") // Generate a valid proof using the challenge-response system validProof := authManager.GenerateLocationChallenge(location.ID) @@ -100,12 +99,12 @@ func TestVisitVerification(t *testing.T) { func TestRecentVisits(t *testing.T) { - creator := std.Address("g1creator123") + creator := address("g1creator123") location1 := locationManager.AddLocation(creator, "Location 1", "Test", 48.8566, 2.3522, "test") location2 := locationManager.AddLocation(creator, "Location 2", "Test", 48.8600, 2.3500, "test") - user1 := std.Address("g1user1") - user2 := std.Address("g1user2") + user1 := address("g1user1") + user2 := address("g1user2") // Create visits with slight time differences visit1 := visitManager.CheckIn(user1, location1.ID, "proof1") @@ -130,10 +129,10 @@ func TestRecentVisits(t *testing.T) { func TestVisitNotes(t *testing.T) { - creator := std.Address("g1creator123") + creator := address("g1creator123") location := locationManager.AddLocation(creator, "Test Location", "Test", 48.8566, 2.3522, "test") - user := std.Address("g1user123") + user := address("g1user123") visit := visitManager.CheckIn(user, location.ID, "test-proof") if visit == nil { @@ -153,7 +152,7 @@ func TestVisitNotes(t *testing.T) { } // Test unauthorized note addition - otherUser := std.Address("g1other123") + otherUser := address("g1other123") unauthorized := visitManager.AddVisitNotes(visit.ID, "Unauthorized", otherUser) if unauthorized { t.Error("Unauthorized user should not be able to add notes") From fc682d3447d725eafa5cca535cf9b6d36ec56346 Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Wed, 29 Oct 2025 11:02:07 +0100 Subject: [PATCH 06/25] test: add unit tests for transaction link generation in geo-resto package --- .../r/karma1337/geo-resto/txlink_test.gno | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 packages/r/karma1337/geo-resto/txlink_test.gno diff --git a/packages/r/karma1337/geo-resto/txlink_test.gno b/packages/r/karma1337/geo-resto/txlink_test.gno new file mode 100644 index 0000000..fc80540 --- /dev/null +++ b/packages/r/karma1337/geo-resto/txlink_test.gno @@ -0,0 +1,132 @@ +package georesto + +import ( + "strings" + "testing" +) + +func TestGetAddLocationTxLink(t *testing.T) { + link := GetAddLocationTxLink("Test Location", "A test", 48.8566, 2.3522, "restaurant") + + // Verify the link contains expected components + if !strings.Contains(link, "$help&func=AddLocationStr") { + t.Error("Link should contain function name AddLocationStr") + } + + if !strings.Contains(link, "name=Test") { + t.Error("Link should contain location name parameter") + } + + if !strings.Contains(link, "latStr=48.8") { + t.Error("Link should contain latStr parameter") + } + + if !strings.Contains(link, "category=restaurant") { + t.Error("Link should contain category parameter") + } +} + +func TestGetCheckInTxLink(t *testing.T) { + locationID := "loc_123" + proof := "test_proof" + + link := GetCheckInTxLink(locationID, proof) + + if !strings.Contains(link, "$help&func=CheckIn") { + t.Error("Link should contain function name CheckIn") + } + + if !strings.Contains(link, "locationID=loc_123") { + t.Error("Link should contain locationID parameter") + } + + if !strings.Contains(link, "proof=test_proof") { + t.Error("Link should contain proof parameter") + } +} + +func TestGetCreateEventTxLink(t *testing.T) { + link := GetCreateEventTxLink("loc_1", "Test Event", "Description", "pass123", 1, 1000, 2000) + + if !strings.Contains(link, "$help&func=CreateEventStr") { + t.Error("Link should contain function name CreateEventStr") + } + + if !strings.Contains(link, "locationID=loc_1") { + t.Error("Link should contain locationID parameter") + } + + if !strings.Contains(link, "name=Test") { + t.Error("Link should contain event name parameter") + } + + if !strings.Contains(link, "eventTypeStr=1") { + t.Error("Link should contain eventTypeStr parameter") + } + + if !strings.Contains(link, "startTimeStr=1000") { + t.Error("Link should contain startTimeStr parameter") + } +} + +func TestGetJoinEventTxLink(t *testing.T) { + eventID := "event_456" + password := "secret" + + link := GetJoinEventTxLink(eventID, password) + + if !strings.Contains(link, "$help&func=JoinEvent") { + t.Error("Link should contain function name JoinEvent") + } + + if !strings.Contains(link, "eventID=event_456") { + t.Error("Link should contain eventID parameter") + } + + if !strings.Contains(link, "password=secret") { + t.Error("Link should contain password parameter") + } +} + +func TestGetVerifyPresenceTxLink(t *testing.T) { + eventID := "event_789" + code := "verification123" + attendee := "g1attendee" + + link := GetVerifyPresenceTxLink(eventID, code, attendee) + + if !strings.Contains(link, "$help&func=VerifyPresence") { + t.Error("Link should contain function name VerifyPresence") + } + + if !strings.Contains(link, "eventID=event_789") { + t.Error("Link should contain eventID parameter") + } + + if !strings.Contains(link, "verificationCode=verification123") { + t.Error("Link should contain verificationCode parameter") + } + + if !strings.Contains(link, "attendeeAddress=g1attendee") { + t.Error("Link should contain attendeeAddress parameter") + } +} + +func TestTxLinkEmptyValues(t *testing.T) { + // Test that empty values create user-editable fields in wallet + link := GetJoinEventTxLink("event_1", "") + + if !strings.Contains(link, "$help&func=JoinEvent") { + t.Error("Link should contain function name") + } + + if !strings.Contains(link, "eventID=event_1") { + t.Error("Link should contain eventID parameter") + } + + // Empty password should still be in the URL as an empty parameter + // This allows the wallet to prompt the user to fill it in + if !strings.Contains(link, "password=") { + t.Error("Link should contain password parameter (even if empty)") + } +} From 0ef3949280dec543dc6808b02e3cb866524c9dd2 Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Wed, 29 Oct 2025 11:09:52 +0100 Subject: [PATCH 07/25] feat: add string parsing helpers and transaction link generation for geo-resto package --- packages/r/karma1337/geo-resto/geo_resto.gno | 203 ++++++++++++++++++- 1 file changed, 193 insertions(+), 10 deletions(-) diff --git a/packages/r/karma1337/geo-resto/geo_resto.gno b/packages/r/karma1337/geo-resto/geo_resto.gno index fb618e6..24a6a6f 100644 --- a/packages/r/karma1337/geo-resto/geo_resto.gno +++ b/packages/r/karma1337/geo-resto/geo_resto.gno @@ -4,6 +4,8 @@ import ( "chain/runtime" "strconv" "strings" + + "gno.land/p/moul/txlink" ) var ( @@ -49,6 +51,100 @@ func Render(path string) string { } } +// Helper functions for parsing string arguments + +// parseFloat converts a string to float64, returns 0 on error +func parseFloat(s string) float64 { + if s == "" { + return 0 + } + // Simple float parsing (supports decimal notation) + var result float64 + var decimal float64 + var isDecimal bool + var divisor float64 = 1 + negative := false + + if s[0] == '-' { + negative = true + s = s[1:] + } + + for i := 0; i < len(s); i++ { + c := s[i] + if c == '.' { + isDecimal = true + continue + } + if c >= '0' && c <= '9' { + if isDecimal { + decimal = decimal*10 + float64(c-'0') + divisor *= 10 + } else { + result = result*10 + float64(c-'0') + } + } + } + + result += decimal / divisor + if negative { + result = -result + } + return result +} + +// parseInt converts a string to int, returns 0 on error +func parseInt(s string) int { + if s == "" { + return 0 + } + var result int + negative := false + + if s[0] == '-' { + negative = true + s = s[1:] + } + + for i := 0; i < len(s); i++ { + c := s[i] + if c >= '0' && c <= '9' { + result = result*10 + int(c-'0') + } + } + + if negative { + result = -result + } + return result +} + +// parseInt64 converts a string to int64, returns 0 on error +func parseInt64(s string) int64 { + if s == "" { + return 0 + } + var result int64 + negative := false + + if s[0] == '-' { + negative = true + s = s[1:] + } + + for i := 0; i < len(s); i++ { + c := s[i] + if c >= '0' && c <= '9' { + result = result*10 + int64(c-'0') + } + } + + if negative { + result = -result + } + return result +} + // Public API Functions // AddLocation allows adding a new geographic location to the system @@ -61,17 +157,21 @@ func AddLocation(name, description string, latitude, longitude float64, category return "Location added with ID: " + location.ID } -// CheckIn allows a user to check in at a specific location -func CheckIn(locationID, proof string) string { +// AddLocationStr is a string-based wrapper for AddLocation (for txlink compatibility) +// The first parameter (_ realm) is automatically provided by Gno runtime +func AddLocationStr(_ realm, name, description, latStr, lonStr, category string) string { + // Parse latitude and longitude from strings + lat := parseFloat(latStr) + lon := parseFloat(lonStr) + return AddLocation(name, description, lat, lon, category) +} + +// CheckIn records a user's visit to a location with proof of presence +// The first parameter (_ realm) is automatically provided by Gno runtime +func CheckIn(_ realm, locationID, proof string) string { caller := runtime.PreviousRealm().Address() - if !authManager.CheckRateLimit(caller.String(), "check_in", 60) { - return "Rate limit exceeded. Please try again later." - } visit := visitManager.CheckIn(caller, locationID, proof) - if visit == nil { - return "Check-in failed" - } - return "Successfully checked in at location: " + locationID + return "Check-in successful! Visit ID: " + visit.ID } // CreateEvent creates a location-based event (like airdrops) @@ -84,8 +184,18 @@ func CreateEvent(locationID, name, description, password string, eventType int, return "Event created with ID: " + event.ID } +// CreateEventStr is a string-based wrapper for CreateEvent (for txlink compatibility) +// The first parameter (_ realm) is automatically provided by Gno runtime +func CreateEventStr(_ realm, locationID, name, description, password, eventTypeStr, startTimeStr, endTimeStr string) string { + eventType := parseInt(eventTypeStr) + startTime := parseInt64(startTimeStr) + endTime := parseInt64(endTimeStr) + return CreateEvent(locationID, name, description, password, eventType, startTime, endTime) +} + // JoinEvent allows a user to join an event with proper authentication -func JoinEvent(eventID, password string) string { +// The first parameter (_ realm) is automatically provided by Gno runtime +func JoinEvent(_ realm, eventID, password string) string { caller := runtime.PreviousRealm().Address() if !authManager.CheckRateLimit(caller.String(), "join_event:"+eventID, 60) { return "Rate limit exceeded. Please try again later." @@ -127,3 +237,76 @@ func GetEventStats(eventID string) string { func GetLocationChallenge(locationID string) string { return authManager.GenerateLocationChallenge(locationID) } + +// VerifyPresence allows event organizers to verify attendee presence using a verification code +// The first parameter (_ realm) is automatically provided by Gno runtime +func VerifyPresence(_ realm, eventID, verificationCode, attendeeAddress string) string { + caller := runtime.PreviousRealm().Address() + success := eventManager.VerifyAttendeePresence(eventID, verificationCode, caller, attendeeAddress) + if success { + return "Successfully verified attendee: " + attendeeAddress + } + return "Failed to verify attendee" +} + +// Transaction Link Helpers - Generate clickable transaction links for wallet integration + +// GetAddLocationTxLink generates a transaction link for adding a new location +func GetAddLocationTxLink(name, description string, latitude, longitude float64, category string) string { + return txlink.NewLink("AddLocationStr"). + AddArgs( + "name", name, + "description", description, + "latStr", strconv.FormatFloat(latitude, 'f', 6, 64), + "lonStr", strconv.FormatFloat(longitude, 'f', 6, 64), + "category", category, + ). + URL() +} + +// GetCheckInTxLink generates a transaction link for checking in at a location +func GetCheckInTxLink(locationID, proof string) string { + return txlink.NewLink("CheckIn"). + AddArgs( + "locationID", locationID, + "proof", proof, + ). + URL() +} + +// GetCreateEventTxLink generates a transaction link for creating an event +func GetCreateEventTxLink(locationID, name, description, password string, eventType int, startTime, endTime int64) string { + return txlink.NewLink("CreateEventStr"). + AddArgs( + "locationID", locationID, + "name", name, + "description", description, + "password", password, + "eventTypeStr", strconv.Itoa(eventType), + "startTimeStr", strconv.FormatInt(startTime, 10), + "endTimeStr", strconv.FormatInt(endTime, 10), + ). + URL() +} + +// GetJoinEventTxLink generates a transaction link for joining an event +func GetJoinEventTxLink(eventID, password string) string { + return txlink.NewLink("JoinEvent"). + AddArgs( + "eventID", eventID, + "password", password, + ). + URL() +} + +// GetVerifyPresenceTxLink generates a transaction link for verifying attendee presence (organizer only) +func GetVerifyPresenceTxLink(eventID, verificationCode, attendeeAddress string) string { + // This would be used by event organizers to verify attendee presence + return txlink.NewLink("VerifyPresence"). + AddArgs( + "eventID", eventID, + "verificationCode", verificationCode, + "attendeeAddress", attendeeAddress, + ). + URL() +} From e3df41a0464980f269be76e20c28d06800a4118d Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Wed, 29 Oct 2025 11:09:59 +0100 Subject: [PATCH 08/25] feat: add quick action links for adding locations and checking in to events --- packages/r/karma1337/geo-resto/renderer.gno | 28 ++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/r/karma1337/geo-resto/renderer.gno b/packages/r/karma1337/geo-resto/renderer.gno index ec54d05..65deb17 100644 --- a/packages/r/karma1337/geo-resto/renderer.gno +++ b/packages/r/karma1337/geo-resto/renderer.gno @@ -37,6 +37,11 @@ func (r *Renderer) RenderMainPage() string { sb.WriteString("- [Active Events](/r/karma1337/georesto:events)\n") sb.WriteString("- [World Map](/r/karma1337/georesto:map)\n\n") + // Quick Actions + sb.WriteString("## πŸ“² Quick Actions\n") + addLocationLink := GetAddLocationTxLink("", "", 0, 0, "") + sb.WriteString("- [βž• Add New Location](" + addLocationLink + ")\n\n") + // Recent activity sb.WriteString("## πŸ”₯ Recent Activity\n") recentVisits := visitManager.GetRecentVisits(5) @@ -112,6 +117,16 @@ func (r *Renderer) RenderLocation(locationID string) string { } sb.WriteString("\n") + // Add transaction link for check-in + sb.WriteString("## πŸ“² Actions\n\n") + challenge := authManager.GenerateLocationChallenge(locationID) + checkInLink := GetCheckInTxLink(locationID, challenge) + sb.WriteString("- [πŸ”” Check In Here](" + checkInLink + ")\n") + + // Add create event link + createEventLink := GetCreateEventTxLink(locationID, "", "", "", 0, 0, 0) + sb.WriteString("- [🎯 Create Event Here](" + createEventLink + ")\n\n") + // Recent visits visits := visitManager.GetLocationVisits(locationID) sb.WriteString("## πŸ”„ Recent Visits (" + strconv.Itoa(len(visits)) + ")\n\n") @@ -245,7 +260,7 @@ func (r *Renderer) RenderEvent(eventID string) string { location := locationManager.GetLocation(event.LocationID) if location != nil { - sb.WriteString("[" + location.Name + "](/r/karma1337/geo-resto:locations/" + event.LocationID + ")") + sb.WriteString("[" + location.Name + "](/r/karma1337/georesto:locations/" + event.LocationID + ")") } else { sb.WriteString(event.LocationID) } @@ -276,6 +291,13 @@ func (r *Renderer) RenderEvent(eventID string) string { sb.WriteString("\n") + // Add transaction link for joining event + if event.IsActive { + sb.WriteString("## πŸ“² Actions\n\n") + joinLink := GetJoinEventTxLink(eventID, "") + sb.WriteString("- [🎟️ Join Event](" + joinLink + ")\n\n") + } + // QR Code and Verification Info (for event organizers) sb.WriteString("## πŸ“± Event Verification\n\n") sb.WriteString("**QR Code**: `" + event.QRCode + "`\n\n") @@ -348,7 +370,7 @@ func (r *Renderer) RenderWorldMap() string { func (r *Renderer) renderLocationCard(location *Location) string { var sb strings.Builder - sb.WriteString("### [" + location.Name + "](/r/karma1337/geo-resto:locations/" + location.ID + ")\n") + sb.WriteString("### [" + location.Name + "](/r/karma1337/georesto:locations/" + location.ID + ")\n") sb.WriteString(location.Description + "\n\n") sb.WriteString("πŸ“ *" + strconv.FormatFloat(location.Latitude, 'f', 4, 64) + ", " + strconv.FormatFloat(location.Longitude, 'f', 4, 64) + "*") sb.WriteString(" | πŸ‘₯ " + strconv.Itoa(location.VisitCount) + " visits") @@ -363,7 +385,7 @@ func (r *Renderer) renderLocationCard(location *Location) string { func (r *Renderer) renderEventCard(event *Event) string { var sb strings.Builder - sb.WriteString("### [" + event.Name + "](/r/karma1337/geo-resto:events/" + event.ID + ")\n") + sb.WriteString("### [" + event.Name + "](/r/karma1337/georesto:events/" + event.ID + ")\n") sb.WriteString(event.Description + "\n\n") sb.WriteString("πŸ“… " + r.formatTimestamp(event.StartTime) + " - " + r.formatTimestamp(event.EndTime) + "\n") sb.WriteString("πŸ‘₯ " + strconv.Itoa(len(event.Participants)) + " participants") From 3297a28936b629e0f1c83425a239983ee140e103 Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Tue, 11 Nov 2025 09:07:28 +0100 Subject: [PATCH 09/25] refactor: remove obsolete transaction link tests from geo-resto package --- .../r/karma1337/geo-resto/txlink_test.gno | 132 ------------------ 1 file changed, 132 deletions(-) delete mode 100644 packages/r/karma1337/geo-resto/txlink_test.gno diff --git a/packages/r/karma1337/geo-resto/txlink_test.gno b/packages/r/karma1337/geo-resto/txlink_test.gno deleted file mode 100644 index fc80540..0000000 --- a/packages/r/karma1337/geo-resto/txlink_test.gno +++ /dev/null @@ -1,132 +0,0 @@ -package georesto - -import ( - "strings" - "testing" -) - -func TestGetAddLocationTxLink(t *testing.T) { - link := GetAddLocationTxLink("Test Location", "A test", 48.8566, 2.3522, "restaurant") - - // Verify the link contains expected components - if !strings.Contains(link, "$help&func=AddLocationStr") { - t.Error("Link should contain function name AddLocationStr") - } - - if !strings.Contains(link, "name=Test") { - t.Error("Link should contain location name parameter") - } - - if !strings.Contains(link, "latStr=48.8") { - t.Error("Link should contain latStr parameter") - } - - if !strings.Contains(link, "category=restaurant") { - t.Error("Link should contain category parameter") - } -} - -func TestGetCheckInTxLink(t *testing.T) { - locationID := "loc_123" - proof := "test_proof" - - link := GetCheckInTxLink(locationID, proof) - - if !strings.Contains(link, "$help&func=CheckIn") { - t.Error("Link should contain function name CheckIn") - } - - if !strings.Contains(link, "locationID=loc_123") { - t.Error("Link should contain locationID parameter") - } - - if !strings.Contains(link, "proof=test_proof") { - t.Error("Link should contain proof parameter") - } -} - -func TestGetCreateEventTxLink(t *testing.T) { - link := GetCreateEventTxLink("loc_1", "Test Event", "Description", "pass123", 1, 1000, 2000) - - if !strings.Contains(link, "$help&func=CreateEventStr") { - t.Error("Link should contain function name CreateEventStr") - } - - if !strings.Contains(link, "locationID=loc_1") { - t.Error("Link should contain locationID parameter") - } - - if !strings.Contains(link, "name=Test") { - t.Error("Link should contain event name parameter") - } - - if !strings.Contains(link, "eventTypeStr=1") { - t.Error("Link should contain eventTypeStr parameter") - } - - if !strings.Contains(link, "startTimeStr=1000") { - t.Error("Link should contain startTimeStr parameter") - } -} - -func TestGetJoinEventTxLink(t *testing.T) { - eventID := "event_456" - password := "secret" - - link := GetJoinEventTxLink(eventID, password) - - if !strings.Contains(link, "$help&func=JoinEvent") { - t.Error("Link should contain function name JoinEvent") - } - - if !strings.Contains(link, "eventID=event_456") { - t.Error("Link should contain eventID parameter") - } - - if !strings.Contains(link, "password=secret") { - t.Error("Link should contain password parameter") - } -} - -func TestGetVerifyPresenceTxLink(t *testing.T) { - eventID := "event_789" - code := "verification123" - attendee := "g1attendee" - - link := GetVerifyPresenceTxLink(eventID, code, attendee) - - if !strings.Contains(link, "$help&func=VerifyPresence") { - t.Error("Link should contain function name VerifyPresence") - } - - if !strings.Contains(link, "eventID=event_789") { - t.Error("Link should contain eventID parameter") - } - - if !strings.Contains(link, "verificationCode=verification123") { - t.Error("Link should contain verificationCode parameter") - } - - if !strings.Contains(link, "attendeeAddress=g1attendee") { - t.Error("Link should contain attendeeAddress parameter") - } -} - -func TestTxLinkEmptyValues(t *testing.T) { - // Test that empty values create user-editable fields in wallet - link := GetJoinEventTxLink("event_1", "") - - if !strings.Contains(link, "$help&func=JoinEvent") { - t.Error("Link should contain function name") - } - - if !strings.Contains(link, "eventID=event_1") { - t.Error("Link should contain eventID parameter") - } - - // Empty password should still be in the URL as an empty parameter - // This allows the wallet to prompt the user to fill it in - if !strings.Contains(link, "password=") { - t.Error("Link should contain password parameter (even if empty)") - } -} From 3004e9166a595d19e247bf3ea89963a65847ea8a Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Tue, 11 Nov 2025 09:25:26 +0100 Subject: [PATCH 10/25] refactor: streamline AuthManager by replacing maps with AVL trees for deterministic outcome --- packages/r/karma1337/geo-resto/auth.gno | 102 +++++++----------------- 1 file changed, 28 insertions(+), 74 deletions(-) diff --git a/packages/r/karma1337/geo-resto/auth.gno b/packages/r/karma1337/geo-resto/auth.gno index 44fd3e3..658eea8 100644 --- a/packages/r/karma1337/geo-resto/auth.gno +++ b/packages/r/karma1337/geo-resto/auth.gno @@ -6,65 +6,36 @@ import ( "math" "strconv" "time" + + "gno.land/p/nt/avl" + "gno.land/p/nt/ownable/exts/authorizable" ) // AuthManager handles access control and proof verification type AuthManager struct { - trustedVerifiers map[string]bool // Trusted verifier addresses - adminAddresses map[string]bool // Admin addresses - rateLimitTracker map[string]int64 // Tracks last action time for a user and action - accessTokens map[string]int64 // token -> expiration time + *authorizable.Authorizable // Handles admin/verifier authorization + rateLimitTracker *avl.Tree // key (user:action) -> timestamp + accessTokens *avl.Tree // token -> expiration time } // NewAuthManager creates a new authentication manager instance func NewAuthManager() *AuthManager { return &AuthManager{ - trustedVerifiers: make(map[string]bool), - adminAddresses: make(map[string]bool), - rateLimitTracker: make(map[string]int64), - accessTokens: make(map[string]int64), - } -} - -// AddTrustedVerifier adds a trusted verifier address -func (am *AuthManager) AddTrustedVerifier(verifierAddress string, admin address) bool { - if !am.IsAdmin(admin.String()) { - return false + Authorizable: authorizable.NewAuthorizable(), + rateLimitTracker: avl.NewTree(), + accessTokens: avl.NewTree(), } - - am.trustedVerifiers[verifierAddress] = true - return true } -// RemoveTrustedVerifier removes a trusted verifier address -func (am *AuthManager) RemoveTrustedVerifier(verifierAddress string, admin address) bool { - if !am.IsAdmin(admin.String()) { - return false - } - - delete(am.trustedVerifiers, verifierAddress) - return true +// IsTrustedVerifier checks if an address is authorized (verifier) +func (am *AuthManager) IsTrustedVerifier(addr string) bool { + return am.Authorizable.PreviousOnAuthList() == nil } -// IsTrustedVerifier checks if an address is a trusted verifier -func (am *AuthManager) IsTrustedVerifier(address string) bool { - return am.trustedVerifiers[address] -} - -// AddAdmin adds an admin address -func (am *AuthManager) AddAdmin(adminAddress string, currentAdmin address) bool { - // Only existing admins can add new admins - if len(am.adminAddresses) > 0 && !am.IsAdmin(currentAdmin.String()) { - return false - } - - am.adminAddresses[adminAddress] = true - return true -} - -// IsAdmin checks if an address is an admin -func (am *AuthManager) IsAdmin(address string) bool { - return am.adminAddresses[address] +// IsAdmin checks if an address is an admin (owner or authorized) +func (am *AuthManager) IsAdmin(addr string) bool { + addrType := address(addr) + return am.Authorizable.Owner() == addrType || am.Authorizable.PreviousOnAuthList() == nil } // VerifyLocationProof verifies a cryptographic proof for location check-in @@ -79,23 +50,6 @@ func (am *AuthManager) GenerateLocationProof(userAddress, locationID string, tim return am.generateLocationProof(userAddress, locationID, timestamp) } -// VerifyEventAccess verifies access to a password-protected event -func (am *AuthManager) VerifyEventAccess(eventID, password string, user address) bool { - event := eventManager.GetEvent(eventID) - if event == nil { - return false - } - - // If no password required, access is granted - if event.Password == "" { - return true - } - - // Verify password - hashedPassword := am.hashString(password) - return hashedPassword == event.Password -} - // CanModifyLocation checks if a user can modify a location func (am *AuthManager) CanModifyLocation(locationID string, user address) bool { location := locationManager.GetLocation(locationID) @@ -161,41 +115,41 @@ func (am *AuthManager) GenerateAccessToken(userAddress string, expirationTime in data := userAddress + ":" + strconv.FormatInt(expirationTime, 10) hash := sha256.Sum256([]byte(data)) token := hex.EncodeToString(hash[:]) - am.accessTokens[token] = expirationTime + am.accessTokens.Set(token, expirationTime) return token } // VerifyAccessToken verifies an access token func (am *AuthManager) VerifyAccessToken(token, userAddress string, currentTime int64) bool { - expirationTime, exists := am.accessTokens[token] + expirationTimeValue, exists := am.accessTokens.Get(token) if !exists { return false // Token does not exist } + expirationTime := expirationTimeValue.(int64) if currentTime > expirationTime { - delete(am.accessTokens, token) // Clean up expired token - return false // Token has expired + am.accessTokens.Remove(token) // Clean up expired token + return false // Token has expired } - // Optional: Verify that the token was generated for the correct userAddress. - // This would require storing the userAddress with the token or regenerating - // the token to check for a match. For now, we'll keep it simple. - return true } // CheckRateLimit implements basic rate limiting for API calls func (am *AuthManager) CheckRateLimit(userAddress string, action string, cooldownSeconds int64) bool { key := userAddress + ":" + action - lastActionTime, exists := am.rateLimitTracker[key] + lastActionTimeValue, exists := am.rateLimitTracker.Get(key) currentTime := time.Now().Unix() - if exists && (currentTime-lastActionTime) < cooldownSeconds { - return false // Rate limit exceeded + if exists { + lastActionTime := lastActionTimeValue.(int64) + if (currentTime - lastActionTime) < cooldownSeconds { + return false // Rate limit exceeded + } } - am.rateLimitTracker[key] = currentTime + am.rateLimitTracker.Set(key, currentTime) return true } From 7c7c00662c1759cc6efbb47db4d4add4215d8d64 Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Tue, 11 Nov 2025 09:25:56 +0100 Subject: [PATCH 11/25] refactor: replace maps with AVL trees in EventManager --- packages/r/karma1337/geo-resto/event.gno | 84 ++++++++++++------------ 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/packages/r/karma1337/geo-resto/event.gno b/packages/r/karma1337/geo-resto/event.gno index 99fc56f..3676e72 100644 --- a/packages/r/karma1337/geo-resto/event.gno +++ b/packages/r/karma1337/geo-resto/event.gno @@ -6,6 +6,8 @@ import ( "strconv" "strings" "time" + + "gno.land/p/nt/avl" ) // EventType defines the type of location-based event @@ -28,7 +30,6 @@ type Event struct { Name string // Event name Description string // Event description EventType EventType // Type of event - Password string // Access password (hashed) StartTime int64 // Event start timestamp EndTime int64 // Event end timestamp CreatedAt int64 // Creation timestamp @@ -43,26 +44,26 @@ type Event struct { // EventManager handles all event-related operations type EventManager struct { - events map[string]*Event // Event ID -> Event - eventsByLocation map[string][]string // Location ID -> Event IDs - eventsByCreator map[string][]string // Creator address -> Event IDs - activeEvents []string // Currently active event IDs + events *avl.Tree // Event ID -> Event + eventsByLocation *avl.Tree // Location ID -> []string (Event IDs) + eventsByCreator *avl.Tree // Creator address -> []string (Event IDs) + activeEvents []string // Currently active event IDs nextID int } // NewEventManager creates a new event manager instance func NewEventManager() *EventManager { return &EventManager{ - events: make(map[string]*Event), - eventsByLocation: make(map[string][]string), - eventsByCreator: make(map[string][]string), + events: avl.NewTree(), + eventsByLocation: avl.NewTree(), + eventsByCreator: avl.NewTree(), activeEvents: make([]string, 0), nextID: 1, } } -// CreateEvent creates a new event -func (em *EventManager) CreateEvent(creator address, locationID, name, description, password string, eventType int, startTime, endTime int64) *Event { +// CreateEvent creates a new event (password removed for security) +func (em *EventManager) CreateEvent(creator address, locationID, name, description string, eventType int, startTime, endTime int64) *Event { // Verify the location exists location := locationManager.GetLocation(locationID) if location == nil { @@ -87,12 +88,6 @@ func (em *EventManager) CreateEvent(creator address, locationID, name, descripti eventID := "event_" + strconv.Itoa(em.nextID) em.nextID++ - // Hash the password for security - hashedPassword := "" - if password != "" { - hashedPassword = em.hashPassword(password) - } - // Generate verification secret for QR codes verificationSecret := em.generateVerificationSecret(eventID, creator.String()) @@ -106,7 +101,6 @@ func (em *EventManager) CreateEvent(creator address, locationID, name, descripti Name: strings.TrimSpace(name), Description: strings.TrimSpace(description), EventType: EventType(eventType), - Password: hashedPassword, StartTime: startTime, EndTime: endTime, CreatedAt: currentTime, @@ -120,14 +114,18 @@ func (em *EventManager) CreateEvent(creator address, locationID, name, descripti } // Store the event - em.events[eventID] = event + em.events.Set(eventID, event) // Index by location - em.eventsByLocation[locationID] = append(em.eventsByLocation[locationID], eventID) + locationEvents := em.getEventIDsForLocation(locationID) + locationEvents = append(locationEvents, eventID) + em.eventsByLocation.Set(locationID, locationEvents) // Index by creator creatorAddr := creator.String() - em.eventsByCreator[creatorAddr] = append(em.eventsByCreator[creatorAddr], eventID) + creatorEvents := em.getEventIDsForCreator(creatorAddr) + creatorEvents = append(creatorEvents, eventID) + em.eventsByCreator.Set(creatorAddr, creatorEvents) // Activate event if it should start now em.updateEventStatus(event) @@ -136,7 +134,7 @@ func (em *EventManager) CreateEvent(creator address, locationID, name, descripti } // JoinEvent allows a user to join an event -func (em *EventManager) JoinEvent(eventID, password string, user address) bool { +func (em *EventManager) JoinEvent(eventID string, user address) bool { event := em.GetEvent(eventID) if event == nil { return false @@ -148,13 +146,6 @@ func (em *EventManager) JoinEvent(eventID, password string, user address) bool { return false } - // Verify password if required - if event.Password != "" { - if !em.verifyPassword(password, event.Password) { - return false - } - } - userAddr := user.String() // Check if user is already a participant @@ -176,11 +167,11 @@ func (em *EventManager) JoinEvent(eventID, password string, user address) bool { // GetEvent retrieves an event by ID func (em *EventManager) GetEvent(eventID string) *Event { - event, exists := em.events[eventID] + eventValue, exists := em.events.Get(eventID) if !exists { return nil } - return event + return eventValue.(*Event) } // GetActiveEvents returns all currently active events @@ -198,8 +189,8 @@ func (em *EventManager) GetActiveEvents() []*Event { // GetLocationEvents returns all events for a specific location func (em *EventManager) GetLocationEvents(locationID string) []*Event { - eventIDs, exists := em.eventsByLocation[locationID] - if !exists { + eventIDs := em.getEventIDsForLocation(locationID) + if len(eventIDs) == 0 { return []*Event{} } @@ -212,6 +203,24 @@ func (em *EventManager) GetLocationEvents(locationID string) []*Event { return events } +// Helper: getEventIDsForLocation retrieves event IDs for a location +func (em *EventManager) getEventIDsForLocation(locationID string) []string { + value, exists := em.eventsByLocation.Get(locationID) + if !exists { + return []string{} + } + return value.([]string) +} + +// Helper: getEventIDsForCreator retrieves event IDs for a creator +func (em *EventManager) getEventIDsForCreator(creatorAddr string) []string { + value, exists := em.eventsByCreator.Get(creatorAddr) + if !exists { + return []string{} + } + return value.([]string) +} + // GetCreatorEvents returns all events by a specific creator func (em *EventManager) GetCreatorEvents(creatorAddress string) []*Event { eventIDs, exists := em.eventsByCreator[creatorAddress] @@ -293,17 +302,6 @@ func (em *EventManager) updateActiveEvents() { } } -// hashPassword hashes a password using SHA256 -func (em *EventManager) hashPassword(password string) string { - hash := sha256.Sum256([]byte(password)) - return hex.EncodeToString(hash[:]) -} - -// verifyPassword verifies a password against a hash -func (em *EventManager) verifyPassword(password, hash string) bool { - return em.hashPassword(password) == hash -} - // GetEventTypeString returns the string representation of an event type func (em *EventManager) GetEventTypeString(eventType EventType) string { switch eventType { From 29a47eb0a2fcbc23ca7d921a9a5048686c46b40a Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Tue, 11 Nov 2025 09:26:10 +0100 Subject: [PATCH 12/25] refactor: replace maps with AVL trees in LocationManager --- packages/r/karma1337/geo-resto/location.gno | 38 ++++++++++++++------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/packages/r/karma1337/geo-resto/location.gno b/packages/r/karma1337/geo-resto/location.gno index 39413f6..764410b 100644 --- a/packages/r/karma1337/geo-resto/location.gno +++ b/packages/r/karma1337/geo-resto/location.gno @@ -4,6 +4,8 @@ import ( "strconv" "strings" "time" + + "gno.land/p/nt/avl" ) // Location represents a geographic point with metadata @@ -22,16 +24,16 @@ type Location struct { // LocationManager handles all location-related operations type LocationManager struct { - locations map[string]*Location // ID -> Location - locationsByUser map[string][]string // User address -> Location IDs + locations *avl.Tree // ID -> Location + locationsByUser *avl.Tree // User address -> []string (Location IDs) nextID int } // NewLocationManager creates a new location manager instance func NewLocationManager() *LocationManager { return &LocationManager{ - locations: make(map[string]*Location), - locationsByUser: make(map[string][]string), + locations: avl.NewTree(), + locationsByUser: avl.NewTree(), nextID: 1, } } @@ -65,33 +67,45 @@ func (lm *LocationManager) AddLocation(creator address, name, description string Verified: false, } - lm.locations[id] = location + lm.locations.Set(id, location) // Track locations by user userAddr := creator.String() - lm.locationsByUser[userAddr] = append(lm.locationsByUser[userAddr], id) + userLocations := lm.getUserLocationIDs(userAddr) + userLocations = append(userLocations, id) + lm.locationsByUser.Set(userAddr, userLocations) return location } // GetLocation retrieves a location by its ID func (lm *LocationManager) GetLocation(id string) *Location { - location, exists := lm.locations[id] + locationValue, exists := lm.locations.Get(id) if !exists { return nil } - return location + return locationValue.(*Location) } // GetAllLocations returns all locations func (lm *LocationManager) GetAllLocations() []*Location { - all := make([]*Location, 0, len(lm.locations)) - for _, location := range lm.locations { - all = append(all, location) - } + all := make([]*Location, 0) + lm.locations.Iterate("", "", func(key string, value interface{}) bool { + all = append(all, value.(*Location)) + return false // continue iteration + }) return all } +// Helper: getUserLocationIDs retrieves location IDs for a user +func (lm *LocationManager) getUserLocationIDs(userAddr string) []string { + value, exists := lm.locationsByUser.Get(userAddr) + if !exists { + return []string{} + } + return value.([]string) +} + // GetUserLocations returns all locations added by a specific user func (lm *LocationManager) GetUserLocations(userAddress string) []*Location { locationIDs, exists := lm.locationsByUser[userAddress] From 10e72015b7cf1aec2d3c224b41181f2b12edce0c Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Tue, 11 Nov 2025 09:40:08 +0100 Subject: [PATCH 13/25] refactor: replace maps with AVL trees in VisitManager --- packages/r/karma1337/geo-resto/visit.gno | 68 ++++++++++++++++-------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/packages/r/karma1337/geo-resto/visit.gno b/packages/r/karma1337/geo-resto/visit.gno index 59b69e3..06f04a6 100644 --- a/packages/r/karma1337/geo-resto/visit.gno +++ b/packages/r/karma1337/geo-resto/visit.gno @@ -5,6 +5,8 @@ import ( "encoding/hex" "strconv" "time" + + "gno.land/p/nt/avl" ) // Visit represents a user's check-in at a location @@ -21,18 +23,18 @@ type Visit struct { // VisitManager handles all visit and check-in related operations type VisitManager struct { - visits map[string]*Visit // Visit ID -> Visit - visitsByUser map[string][]string // User address -> Visit IDs - visitsByLocation map[string][]string // Location ID -> Visit IDs + visits *avl.Tree // Visit ID -> Visit + visitsByUser *avl.Tree // User address -> []string (Visit IDs) + visitsByLocation *avl.Tree // Location ID -> []string (Visit IDs) nextID int } // NewVisitManager creates a new visit manager instance func NewVisitManager() *VisitManager { return &VisitManager{ - visits: make(map[string]*Visit), - visitsByUser: make(map[string][]string), - visitsByLocation: make(map[string][]string), + visits: avl.NewTree(), + visitsByUser: avl.NewTree(), + visitsByLocation: avl.NewTree(), nextID: 1, } } @@ -72,13 +74,17 @@ func (vm *VisitManager) CheckIn(user address, locationID, proof string) *Visit { } // Store the visit - vm.visits[visitID] = visit + vm.visits.Set(visitID, visit) // Index by user - vm.visitsByUser[userAddr] = append(vm.visitsByUser[userAddr], visitID) + userVisits := vm.getUserVisitIDs(userAddr) + userVisits = append(userVisits, visitID) + vm.visitsByUser.Set(userAddr, userVisits) // Index by location - vm.visitsByLocation[locationID] = append(vm.visitsByLocation[locationID], visitID) + locationVisits := vm.getLocationVisitIDs(locationID) + locationVisits = append(locationVisits, visitID) + vm.visitsByLocation.Set(locationID, locationVisits) // Update location visit count locationManager.IncrementVisitCount(locationID) @@ -88,17 +94,17 @@ func (vm *VisitManager) CheckIn(user address, locationID, proof string) *Visit { // GetVisit retrieves a visit by ID func (vm *VisitManager) GetVisit(visitID string) *Visit { - visit, exists := vm.visits[visitID] + visitValue, exists := vm.visits.Get(visitID) if !exists { return nil } - return visit + return visitValue.(*Visit) } // GetUserVisits returns all visits by a specific user func (vm *VisitManager) GetUserVisits(userAddress string) []*Visit { - visitIDs, exists := vm.visitsByUser[userAddress] - if !exists { + visitIDs := vm.getUserVisitIDs(userAddress) + if len(visitIDs) == 0 { return []*Visit{} } @@ -113,8 +119,8 @@ func (vm *VisitManager) GetUserVisits(userAddress string) []*Visit { // GetLocationVisits returns all visits to a specific location func (vm *VisitManager) GetLocationVisits(locationID string) []*Visit { - visitIDs, exists := vm.visitsByLocation[locationID] - if !exists { + visitIDs := vm.getLocationVisitIDs(locationID) + if len(visitIDs) == 0 { return []*Visit{} } @@ -129,19 +135,17 @@ func (vm *VisitManager) GetLocationVisits(locationID string) []*Visit { // GetUserVisitCount returns the total number of visits by a user func (vm *VisitManager) GetUserVisitCount(userAddress string) int { - visitIDs, exists := vm.visitsByUser[userAddress] - if !exists { - return 0 - } + visitIDs := vm.getUserVisitIDs(userAddress) return len(visitIDs) } // GetRecentVisits returns the most recent visits across all users func (vm *VisitManager) GetRecentVisits(limit int) []*Visit { - allVisits := make([]*Visit, 0, len(vm.visits)) - for _, visit := range vm.visits { - allVisits = append(allVisits, visit) - } + allVisits := make([]*Visit, 0) + vm.visits.Iterate("", "", func(key string, value interface{}) bool { + allVisits = append(allVisits, value.(*Visit)) + return false // continue iteration + }) // Use a more efficient sorting algorithm (insertion sort) for i := 1; i < len(allVisits); i++ { @@ -214,3 +218,21 @@ func (vm *VisitManager) AddVisitNotes(visitID, notes string, user address) bool visit.Notes = notes return true } + +// Helper: getUserVisitIDs retrieves visit IDs for a user +func (vm *VisitManager) getUserVisitIDs(userAddress string) []string { + value, exists := vm.visitsByUser.Get(userAddress) + if !exists { + return []string{} + } + return value.([]string) +} + +// Helper: getLocationVisitIDs retrieves visit IDs for a location +func (vm *VisitManager) getLocationVisitIDs(locationID string) []string { + value, exists := vm.visitsByLocation.Get(locationID) + if !exists { + return []string{} + } + return value.([]string) +} From 50c44720e3d368800acf8cdb5c21b017819a265d Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Tue, 11 Nov 2025 10:02:08 +0100 Subject: [PATCH 14/25] refactor: simplify event creation by removing redundant password parameter in tests --- packages/r/karma1337/geo-resto/auth_test.gno | 33 ++---------- packages/r/karma1337/geo-resto/event_test.gno | 54 ++++--------------- .../geo-resto/qr_verification_test.gno | 5 +- 3 files changed, 14 insertions(+), 78 deletions(-) diff --git a/packages/r/karma1337/geo-resto/auth_test.gno b/packages/r/karma1337/geo-resto/auth_test.gno index 13c97e0..99a548b 100644 --- a/packages/r/karma1337/geo-resto/auth_test.gno +++ b/packages/r/karma1337/geo-resto/auth_test.gno @@ -25,36 +25,9 @@ func TestRateLimiting(t *testing.T) { // because `time.Sleep` is not available and we cannot manipulate the block timestamp. } -func TestAdminPermissions(t *testing.T) { - auth := NewAuthManager() - admin := address("g1admin") - user := address("g1user") - verifier := "g1verifier" - - // Initially, no admins - if auth.IsAdmin(admin.String()) { - t.Error("Should not be admin initially") - } - - // Add admin (first one doesn't require an existing admin) - auth.AddAdmin(admin.String(), address("")) - if !auth.IsAdmin(admin.String()) { - t.Fatal("Failed to add first admin") - } - - // Non-admin cannot add verifier - if auth.AddTrustedVerifier(verifier, user) { - t.Error("Non-admin should not be able to add verifier") - } - - // Admin can add verifier - if !auth.AddTrustedVerifier(verifier, admin) { - t.Error("Admin should be able to add verifier") - } - if !auth.IsTrustedVerifier(verifier) { - t.Error("Failed to verify trusted verifier") - } -} +// The previous admin/verifier test relied on custom AuthManager maps. +// After refactor, authorization is delegated to authorizable (superuser + auth list). +// Here we only sanity-check rate limit & token features remain unaffected. func TestAccessToken(t *testing.T) { auth := NewAuthManager() diff --git a/packages/r/karma1337/geo-resto/event_test.gno b/packages/r/karma1337/geo-resto/event_test.gno index b443606..3e6c2c6 100644 --- a/packages/r/karma1337/geo-resto/event_test.gno +++ b/packages/r/karma1337/geo-resto/event_test.gno @@ -16,16 +16,7 @@ func TestEventManager(t *testing.T) { startTime := time.Now().Unix() + 100 // Start in 100 seconds endTime := startTime + 3600 // End in 1 hour - event := eventManager.CreateEvent( - creator, - location.ID, - "Test Event", - "A test event", - "password123", - int(EventTypeAirdrop), - startTime, - endTime, - ) + event := eventManager.CreateEvent(creator, location.ID, "Test Event", "A test event", int(EventTypeAirdrop), startTime, endTime) if event == nil { t.Fatal("Failed to create event") @@ -54,27 +45,18 @@ func TestEventJoining(t *testing.T) { startTime := time.Now().Unix() // Starts right now endTime := startTime + 3600 // Ends in ~1 hour - event := eventManager.CreateEvent( - creator, - location.ID, - "Active Event", - "An active event", - "password123", - int(EventTypeMeetup), - startTime, - endTime, - ) + event := eventManager.CreateEvent(creator, location.ID, "Active Event", "An active event", int(EventTypeMeetup), startTime, endTime) user := address("g1user123") // Test joining with correct password - success := eventManager.JoinEvent(event.ID, "password123", user) + success := eventManager.JoinEvent(event.ID, user) if !success { t.Error("Should be able to join event with correct password") } // Test joining with incorrect password - success2 := eventManager.JoinEvent(event.ID, "wrongpassword", user) + success2 := eventManager.JoinEvent(event.ID, user) if success2 { t.Error("Should not be able to join event with incorrect password") } @@ -95,16 +77,7 @@ func TestEventStatusUpdates(t *testing.T) { startTime := currentTime + 100 endTime := startTime + 200 - event := eventManager.CreateEvent( - creator, - location.ID, - "Future Event", - "A future event", - "", - int(EventTypeMeetup), - startTime, - endTime, - ) + event := eventManager.CreateEvent(creator, location.ID, "Future Event", "A future event", int(EventTypeMeetup), startTime, endTime) // Event should not be active yet if event.IsActive { @@ -135,9 +108,9 @@ func TestEventsByLocation(t *testing.T) { endTime := startTime + 3600 // Create events for different locations - event1 := eventManager.CreateEvent(creator, location1.ID, "Event 1", "Test", "", int(EventTypeAirdrop), startTime, endTime) - event2 := eventManager.CreateEvent(creator, location1.ID, "Event 2", "Test", "", int(EventTypeMeetup), startTime, endTime) - _ = eventManager.CreateEvent(creator, location2.ID, "Event 3", "Test", "", int(EventTypeContest), startTime, endTime) + event1 := eventManager.CreateEvent(creator, location1.ID, "Event 1", "Test", int(EventTypeAirdrop), startTime, endTime) + event2 := eventManager.CreateEvent(creator, location1.ID, "Event 2", "Test", int(EventTypeMeetup), startTime, endTime) + _ = eventManager.CreateEvent(creator, location2.ID, "Event 3", "Test", int(EventTypeContest), startTime, endTime) // Test getting events by location location1Events := eventManager.GetLocationEvents(location1.ID) @@ -197,14 +170,5 @@ func TestEventValidation(t *testing.T) { }() // This should panic - eventManager.CreateEvent( - creator, - location.ID, - "Invalid Event", - "Past start time", - "", - int(EventTypeAirdrop), - currentTime-100, // Past start time - currentTime+100, - ) + eventManager.CreateEvent(creator, location.ID, "Invalid Event", "Past start time", int(EventTypeAirdrop), currentTime-100, currentTime+100) } diff --git a/packages/r/karma1337/geo-resto/qr_verification_test.gno b/packages/r/karma1337/geo-resto/qr_verification_test.gno index 759a298..abce14a 100644 --- a/packages/r/karma1337/geo-resto/qr_verification_test.gno +++ b/packages/r/karma1337/geo-resto/qr_verification_test.gno @@ -20,7 +20,7 @@ func TestQRCodeVerification(t *testing.T) { startTime := currentTime + 60 // Starts in 1 minute (close enough to current time) endTime := currentTime + 7200 // Ends in 2 hours - event := eventManager.CreateEvent(creator, location.ID, "QR Test Event", "Testing QR verification", "", int(EventTypeMeetup), startTime, endTime) + event := eventManager.CreateEvent(creator, location.ID, "QR Test Event", "Testing QR verification", int(EventTypeMeetup), startTime, endTime) if event == nil { t.Fatal("Failed to create event") } @@ -95,7 +95,6 @@ func TestQRCodeVerification(t *testing.T) { // Add a participant manually (since event might not be active in test) // This simulates what would happen if JoinEvent succeeded event.Participants = append(event.Participants, attendeeAddress) - eventManager.events[event.ID] = event // Save changes total2, verified2, _ := eventManager.GetAttendeeVerificationStats(event.ID, creator) if total2 != 1 || verified2 != 1 { @@ -114,7 +113,7 @@ func TestQRCodeTimeSecurity(t *testing.T) { startTime := time.Now().Unix() + 3600 endTime := startTime + 7200 - event := eventManager.CreateEvent(creator, location.ID, "Time Security Test", "Testing time-based security", "", int(EventTypeMeetup), startTime, endTime) + event := eventManager.CreateEvent(creator, location.ID, "Time Security Test", "Testing time-based security", int(EventTypeMeetup), startTime, endTime) // Generate a verification code code1 := eventManager.GenerateAttendeeVerificationCode(event.ID, creator) From d436ed44fffea2251b4d0990e8657556a4cae6f4 Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Tue, 11 Nov 2025 10:02:22 +0100 Subject: [PATCH 15/25] refactor: remove password parameter from event-related functions for simplification --- packages/r/karma1337/geo-resto/event.gno | 14 ++++++------ packages/r/karma1337/geo-resto/geo_resto.gno | 18 +++++++-------- packages/r/karma1337/geo-resto/location.gno | 24 +++++++++----------- packages/r/karma1337/geo-resto/renderer.gno | 7 ++---- 4 files changed, 28 insertions(+), 35 deletions(-) diff --git a/packages/r/karma1337/geo-resto/event.gno b/packages/r/karma1337/geo-resto/event.gno index 3676e72..4dce957 100644 --- a/packages/r/karma1337/geo-resto/event.gno +++ b/packages/r/karma1337/geo-resto/event.gno @@ -223,11 +223,10 @@ func (em *EventManager) getEventIDsForCreator(creatorAddr string) []string { // GetCreatorEvents returns all events by a specific creator func (em *EventManager) GetCreatorEvents(creatorAddress string) []*Event { - eventIDs, exists := em.eventsByCreator[creatorAddress] - if !exists { + eventIDs := em.getEventIDsForCreator(creatorAddress) + if len(eventIDs) == 0 { return []*Event{} } - events := make([]*Event, 0, len(eventIDs)) for _, id := range eventIDs { if event := em.GetEvent(id); event != nil { @@ -291,15 +290,16 @@ func (em *EventManager) updateEventStatus(event *Event) { func (em *EventManager) updateActiveEvents() { currentTime := time.Now().Unix() em.activeEvents = make([]string, 0) - - for eventID, event := range em.events { + em.events.Iterate("", "", func(key string, value interface{}) bool { + event := value.(*Event) if currentTime >= event.StartTime && currentTime <= event.EndTime { event.IsActive = true - em.activeEvents = append(em.activeEvents, eventID) + em.activeEvents = append(em.activeEvents, key) } else { event.IsActive = false } - } + return false // continue + }) } // GetEventTypeString returns the string representation of an event type diff --git a/packages/r/karma1337/geo-resto/geo_resto.gno b/packages/r/karma1337/geo-resto/geo_resto.gno index 24a6a6f..65ab14b 100644 --- a/packages/r/karma1337/geo-resto/geo_resto.gno +++ b/packages/r/karma1337/geo-resto/geo_resto.gno @@ -175,32 +175,32 @@ func CheckIn(_ realm, locationID, proof string) string { } // CreateEvent creates a location-based event (like airdrops) -func CreateEvent(locationID, name, description, password string, eventType int, startTime, endTime int64) string { +func CreateEvent(locationID, name, description string, eventType int, startTime, endTime int64) string { caller := runtime.PreviousRealm().Address() if !authManager.CheckRateLimit(caller.String(), "create_event", 300) { // 5 minute cooldown return "Rate limit exceeded. Please try again later." } - event := eventManager.CreateEvent(caller, locationID, name, description, password, eventType, startTime, endTime) + event := eventManager.CreateEvent(caller, locationID, name, description, eventType, startTime, endTime) return "Event created with ID: " + event.ID } // CreateEventStr is a string-based wrapper for CreateEvent (for txlink compatibility) // The first parameter (_ realm) is automatically provided by Gno runtime -func CreateEventStr(_ realm, locationID, name, description, password, eventTypeStr, startTimeStr, endTimeStr string) string { +func CreateEventStr(_ realm, locationID, name, description, eventTypeStr, startTimeStr, endTimeStr string) string { eventType := parseInt(eventTypeStr) startTime := parseInt64(startTimeStr) endTime := parseInt64(endTimeStr) - return CreateEvent(locationID, name, description, password, eventType, startTime, endTime) + return CreateEvent(locationID, name, description, eventType, startTime, endTime) } // JoinEvent allows a user to join an event with proper authentication // The first parameter (_ realm) is automatically provided by Gno runtime -func JoinEvent(_ realm, eventID, password string) string { +func JoinEvent(_ realm, eventID string) string { caller := runtime.PreviousRealm().Address() if !authManager.CheckRateLimit(caller.String(), "join_event:"+eventID, 60) { return "Rate limit exceeded. Please try again later." } - success := eventManager.JoinEvent(eventID, password, caller) + success := eventManager.JoinEvent(eventID, caller) if success { return "Successfully joined event: " + eventID } @@ -275,13 +275,12 @@ func GetCheckInTxLink(locationID, proof string) string { } // GetCreateEventTxLink generates a transaction link for creating an event -func GetCreateEventTxLink(locationID, name, description, password string, eventType int, startTime, endTime int64) string { +func GetCreateEventTxLink(locationID, name, description string, eventType int, startTime, endTime int64) string { return txlink.NewLink("CreateEventStr"). AddArgs( "locationID", locationID, "name", name, "description", description, - "password", password, "eventTypeStr", strconv.Itoa(eventType), "startTimeStr", strconv.FormatInt(startTime, 10), "endTimeStr", strconv.FormatInt(endTime, 10), @@ -290,11 +289,10 @@ func GetCreateEventTxLink(locationID, name, description, password string, eventT } // GetJoinEventTxLink generates a transaction link for joining an event -func GetJoinEventTxLink(eventID, password string) string { +func GetJoinEventTxLink(eventID string) string { return txlink.NewLink("JoinEvent"). AddArgs( "eventID", eventID, - "password", password, ). URL() } diff --git a/packages/r/karma1337/geo-resto/location.gno b/packages/r/karma1337/geo-resto/location.gno index 764410b..ed693ad 100644 --- a/packages/r/karma1337/geo-resto/location.gno +++ b/packages/r/karma1337/geo-resto/location.gno @@ -108,11 +108,10 @@ func (lm *LocationManager) getUserLocationIDs(userAddr string) []string { // GetUserLocations returns all locations added by a specific user func (lm *LocationManager) GetUserLocations(userAddress string) []*Location { - locationIDs, exists := lm.locationsByUser[userAddress] - if !exists { + locationIDs := lm.getUserLocationIDs(userAddress) + if len(locationIDs) == 0 { return []*Location{} } - locations := make([]*Location, 0, len(locationIDs)) for _, id := range locationIDs { if location := lm.GetLocation(id); location != nil { @@ -124,10 +123,7 @@ func (lm *LocationManager) GetUserLocations(userAddress string) []*Location { // GetUserLocationCount returns the number of locations added by a user func (lm *LocationManager) GetUserLocationCount(userAddress string) int { - locationIDs, exists := lm.locationsByUser[userAddress] - if !exists { - return 0 - } + locationIDs := lm.getUserLocationIDs(userAddress) return len(locationIDs) } @@ -154,24 +150,26 @@ func (lm *LocationManager) VerifyLocation(id string, verifier address) bool { // GetNearbyLocations returns locations within a given radius using the Haversine formula. func (lm *LocationManager) GetNearbyLocations(latitude, longitude, radiusKm float64) []*Location { nearby := make([]*Location, 0) - - for _, location := range lm.locations { + lm.locations.Iterate("", "", func(key string, value interface{}) bool { + location := value.(*Location) distance := haversine(latitude, longitude, location.Latitude, location.Longitude) if distance <= radiusKm { nearby = append(nearby, location) } - } - + return false + }) return nearby } // GetLocationsByCategory returns all locations in a specific category func (lm *LocationManager) GetLocationsByCategory(category string) []*Location { byCategory := make([]*Location, 0) - for _, location := range lm.locations { + lm.locations.Iterate("", "", func(key string, value interface{}) bool { + location := value.(*Location) if location.Category == category { byCategory = append(byCategory, location) } - } + return false + }) return byCategory } diff --git a/packages/r/karma1337/geo-resto/renderer.gno b/packages/r/karma1337/geo-resto/renderer.gno index 65deb17..cdcdf28 100644 --- a/packages/r/karma1337/geo-resto/renderer.gno +++ b/packages/r/karma1337/geo-resto/renderer.gno @@ -124,7 +124,7 @@ func (r *Renderer) RenderLocation(locationID string) string { sb.WriteString("- [πŸ”” Check In Here](" + checkInLink + ")\n") // Add create event link - createEventLink := GetCreateEventTxLink(locationID, "", "", "", 0, 0, 0) + createEventLink := GetCreateEventTxLink(locationID, "", "", 0, 0, 0) sb.WriteString("- [🎯 Create Event Here](" + createEventLink + ")\n\n") // Recent visits @@ -285,16 +285,13 @@ func (r *Renderer) RenderEvent(eventID string) string { sb.WriteString("**Rewards**: " + event.Rewards + "\n") } - if event.Password != "" { - sb.WriteString("**Access**: πŸ”’ Password required\n") - } sb.WriteString("\n") // Add transaction link for joining event if event.IsActive { sb.WriteString("## πŸ“² Actions\n\n") - joinLink := GetJoinEventTxLink(eventID, "") + joinLink := GetJoinEventTxLink(eventID) sb.WriteString("- [🎟️ Join Event](" + joinLink + ")\n\n") } From 44fa137a7957c89d07e0053c8237386c63f95bf5 Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Tue, 11 Nov 2025 10:05:00 +0100 Subject: [PATCH 16/25] refactor: remove password check from event joining test for simplification --- packages/r/karma1337/geo-resto/event_test.gno | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/r/karma1337/geo-resto/event_test.gno b/packages/r/karma1337/geo-resto/event_test.gno index 3e6c2c6..9282321 100644 --- a/packages/r/karma1337/geo-resto/event_test.gno +++ b/packages/r/karma1337/geo-resto/event_test.gno @@ -55,12 +55,6 @@ func TestEventJoining(t *testing.T) { t.Error("Should be able to join event with correct password") } - // Test joining with incorrect password - success2 := eventManager.JoinEvent(event.ID, user) - if success2 { - t.Error("Should not be able to join event with incorrect password") - } - // Test checking if user is participant isParticipant := eventManager.IsUserParticipant(event.ID, user.String()) if !isParticipant { From 7f49c46ad55dcbcfa54b8d431144ee2e9eca9088 Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Tue, 11 Nov 2025 10:10:41 +0100 Subject: [PATCH 17/25] refactor: adjust formatting --- packages/r/karma1337/geo-resto/auth.gno | 2 +- packages/r/karma1337/geo-resto/renderer.gno | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/r/karma1337/geo-resto/auth.gno b/packages/r/karma1337/geo-resto/auth.gno index 658eea8..d4d6172 100644 --- a/packages/r/karma1337/geo-resto/auth.gno +++ b/packages/r/karma1337/geo-resto/auth.gno @@ -13,7 +13,7 @@ import ( // AuthManager handles access control and proof verification type AuthManager struct { - *authorizable.Authorizable // Handles admin/verifier authorization + *authorizable.Authorizable // Handles admin/verifier authorization rateLimitTracker *avl.Tree // key (user:action) -> timestamp accessTokens *avl.Tree // token -> expiration time } diff --git a/packages/r/karma1337/geo-resto/renderer.gno b/packages/r/karma1337/geo-resto/renderer.gno index cdcdf28..f5656dc 100644 --- a/packages/r/karma1337/geo-resto/renderer.gno +++ b/packages/r/karma1337/geo-resto/renderer.gno @@ -285,13 +285,12 @@ func (r *Renderer) RenderEvent(eventID string) string { sb.WriteString("**Rewards**: " + event.Rewards + "\n") } - sb.WriteString("\n") // Add transaction link for joining event if event.IsActive { sb.WriteString("## πŸ“² Actions\n\n") - joinLink := GetJoinEventTxLink(eventID) + joinLink := GetJoinEventTxLink(eventID) sb.WriteString("- [🎟️ Join Event](" + joinLink + ")\n\n") } From 17e12636c36466449035cb5122b5f50cf590e205 Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Thu, 13 Nov 2025 06:30:45 +0100 Subject: [PATCH 18/25] refactor: enhance error handling in location and event functions; add quick action links in renderer --- packages/r/karma1337/geo-resto/geo_resto.gno | 9 +++++++++ packages/r/karma1337/geo-resto/renderer.gno | 10 ++++++++++ .../r/karma1337/geo-resto/zkproof is hard.md | 16 ++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 packages/r/karma1337/geo-resto/zkproof is hard.md diff --git a/packages/r/karma1337/geo-resto/geo_resto.gno b/packages/r/karma1337/geo-resto/geo_resto.gno index 65ab14b..89b53ec 100644 --- a/packages/r/karma1337/geo-resto/geo_resto.gno +++ b/packages/r/karma1337/geo-resto/geo_resto.gno @@ -154,6 +154,9 @@ func AddLocation(name, description string, latitude, longitude float64, category return "Rate limit exceeded. Please try again later." } location := locationManager.AddLocation(caller, name, description, latitude, longitude, category) + if location == nil { + return "Failed to add location" + } return "Location added with ID: " + location.ID } @@ -171,6 +174,9 @@ func AddLocationStr(_ realm, name, description, latStr, lonStr, category string) func CheckIn(_ realm, locationID, proof string) string { caller := runtime.PreviousRealm().Address() visit := visitManager.CheckIn(caller, locationID, proof) + if visit == nil { + return "Failed to check-in" + } return "Check-in successful! Visit ID: " + visit.ID } @@ -181,6 +187,9 @@ func CreateEvent(locationID, name, description string, eventType int, startTime, return "Rate limit exceeded. Please try again later." } event := eventManager.CreateEvent(caller, locationID, name, description, eventType, startTime, endTime) + if event == nil { + return "Failed to create event" + } return "Event created with ID: " + event.ID } diff --git a/packages/r/karma1337/geo-resto/renderer.gno b/packages/r/karma1337/geo-resto/renderer.gno index f5656dc..4458a35 100644 --- a/packages/r/karma1337/geo-resto/renderer.gno +++ b/packages/r/karma1337/geo-resto/renderer.gno @@ -40,7 +40,13 @@ func (r *Renderer) RenderMainPage() string { // Quick Actions sb.WriteString("## πŸ“² Quick Actions\n") addLocationLink := GetAddLocationTxLink("", "", 0, 0, "") + checkInLink := GetCheckInTxLink("", "") sb.WriteString("- [βž• Add New Location](" + addLocationLink + ")\n\n") + sb.WriteString("- [πŸ”” Check In to a Location](" + checkInLink + ")\n\n") + + // Quick create event (user will fill details in wallet UI) + createEventGenericLink := GetCreateEventTxLink("", "", "", 0, 0, 0) + sb.WriteString("- [🎯 Create Event](" + createEventGenericLink + ")\n\n") // Recent activity sb.WriteString("## πŸ”₯ Recent Activity\n") @@ -298,6 +304,10 @@ func (r *Renderer) RenderEvent(eventID string) string { sb.WriteString("## πŸ“± Event Verification\n\n") sb.WriteString("**QR Code**: `" + event.QRCode + "`\n\n") + // Organizer-only: Generate a Verify Attendee tx link (organizers use this to verify attendees) + verifyLink := GetVerifyPresenceTxLink(event.ID, "", "") + sb.WriteString("- [βœ… Verify Attendee (organizer)](" + verifyLink + ")\n\n") + // Verification Statistics totalParticipants := len(event.Participants) verifiedCount := len(event.VerifiedAttendees) diff --git a/packages/r/karma1337/geo-resto/zkproof is hard.md b/packages/r/karma1337/geo-resto/zkproof is hard.md new file mode 100644 index 0000000..287b514 --- /dev/null +++ b/packages/r/karma1337/geo-resto/zkproof is hard.md @@ -0,0 +1,16 @@ +zkproof is hard + +online event maybe + +dynamic qrcode + +otp auth to proof + +create a transaction link to have an adena popup and do action like create event, register presence, etc etc + +https://github.com/samouraiworld/zenao + +https://gno.land/r/docs/txlink + + +resolve address and retrieve username from address https://gno.land/r/docs/resolveusers \ No newline at end of file From 4b3368c649542ff774c0e74bdb0001f42b559180 Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Sun, 16 Nov 2025 06:52:38 +0100 Subject: [PATCH 19/25] refactor: implement trusted verifier management; add functions to add and remove trusted verifiers --- packages/r/karma1337/geo-resto/auth.gno | 35 +++++++++++++++--- packages/r/karma1337/geo-resto/geo_resto.gno | 38 ++++++++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/packages/r/karma1337/geo-resto/auth.gno b/packages/r/karma1337/geo-resto/auth.gno index d4d6172..1e02e93 100644 --- a/packages/r/karma1337/geo-resto/auth.gno +++ b/packages/r/karma1337/geo-resto/auth.gno @@ -16,6 +16,7 @@ type AuthManager struct { *authorizable.Authorizable // Handles admin/verifier authorization rateLimitTracker *avl.Tree // key (user:action) -> timestamp accessTokens *avl.Tree // token -> expiration time + Authorized *avl.Tree // address -> bool (trusted verifiers) } // NewAuthManager creates a new authentication manager instance @@ -24,18 +25,46 @@ func NewAuthManager() *AuthManager { Authorizable: authorizable.NewAuthorizable(), rateLimitTracker: avl.NewTree(), accessTokens: avl.NewTree(), + Authorized: avl.NewTree(), } } // IsTrustedVerifier checks if an address is authorized (verifier) func (am *AuthManager) IsTrustedVerifier(addr string) bool { - return am.Authorizable.PreviousOnAuthList() == nil + v, ok := am.Authorized.Get(addr) + if !ok { + return false + } + return v.(bool) +} + +// AddTrustedVerifier allows the owner to add an address as a trusted verifier +func (am *AuthManager) AddTrustedVerifier(addr string, caller address) bool { + // Try to add via the external authorizable extension. That extension + // enforces owner-only access internally; we call it and mirror the + // change locally so we can query membership efficiently. + err := am.Authorizable.AddToAuthList(address(addr)) + if err != nil { + return false + } + am.Authorized.Set(addr, true) + return true +} + +// RemoveTrustedVerifier allows the owner to remove a trusted verifier +func (am *AuthManager) RemoveTrustedVerifier(addr string, caller address) bool { + err := am.Authorizable.DeleteFromAuthList(address(addr)) + if err != nil { + return false + } + am.Authorized.Remove(addr) + return true } // IsAdmin checks if an address is an admin (owner or authorized) func (am *AuthManager) IsAdmin(addr string) bool { addrType := address(addr) - return am.Authorizable.Owner() == addrType || am.Authorizable.PreviousOnAuthList() == nil + return am.Authorizable.Owner() == addrType } // VerifyLocationProof verifies a cryptographic proof for location check-in @@ -68,12 +97,10 @@ func (am *AuthManager) CanModifyLocation(locationID string, user address) bool { if am.IsAdmin(userAddr) { return true } - // Trusted verifiers can modify for verification purposes if am.IsTrustedVerifier(userAddr) { return true } - return false } diff --git a/packages/r/karma1337/geo-resto/geo_resto.gno b/packages/r/karma1337/geo-resto/geo_resto.gno index 89b53ec..1fa38c4 100644 --- a/packages/r/karma1337/geo-resto/geo_resto.gno +++ b/packages/r/karma1337/geo-resto/geo_resto.gno @@ -258,6 +258,26 @@ func VerifyPresence(_ realm, eventID, verificationCode, attendeeAddress string) return "Failed to verify attendee" } +// AddTrustedVerifier allows the realm owner to add a trusted verifier (organizer) +func AddTrustedVerifierStr(_ realm, addr string) string { + caller := runtime.PreviousRealm().Address() + success := authManager.AddTrustedVerifier(addr, caller) + if success { + return "Added trusted verifier: " + addr + } + return "Failed to add trusted verifier (owner only)" +} + +// RemoveTrustedVerifier allows the realm owner to remove a trusted verifier +func RemoveTrustedVerifierStr(_ realm, addr string) string { + caller := runtime.PreviousRealm().Address() + success := authManager.RemoveTrustedVerifier(addr, caller) + if success { + return "Removed trusted verifier: " + addr + } + return "Failed to remove trusted verifier (owner only)" +} + // Transaction Link Helpers - Generate clickable transaction links for wallet integration // GetAddLocationTxLink generates a transaction link for adding a new location @@ -317,3 +337,21 @@ func GetVerifyPresenceTxLink(eventID, verificationCode, attendeeAddress string) ). URL() } + +// GetAddTrustedVerifierTxLink generates a transaction link for adding a trusted verifier +func GetAddTrustedVerifierTxLink(addr string) string { + return txlink.NewLink("AddTrustedVerifierStr"). + AddArgs( + "addr", addr, + ). + URL() +} + +// GetRemoveTrustedVerifierTxLink generates a transaction link for removing a trusted verifier +func GetRemoveTrustedVerifierTxLink(addr string) string { + return txlink.NewLink("RemoveTrustedVerifierStr"). + AddArgs( + "addr", addr, + ). + URL() +} From 6350c6c183dea5e111945248573020ac79b6457a Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Mon, 17 Nov 2025 09:50:42 +0100 Subject: [PATCH 20/25] test: add unit tests for trusted verifier management functionality --- .../geo-resto/auth_trusted_verifier_test.gno | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 packages/r/karma1337/geo-resto/auth_trusted_verifier_test.gno diff --git a/packages/r/karma1337/geo-resto/auth_trusted_verifier_test.gno b/packages/r/karma1337/geo-resto/auth_trusted_verifier_test.gno new file mode 100644 index 0000000..4860422 --- /dev/null +++ b/packages/r/karma1337/geo-resto/auth_trusted_verifier_test.gno @@ -0,0 +1,54 @@ +package georesto + +import ( + "testing" +) + +func TestTrustedVerifierOwnerFlow(t *testing.T) { + // Owner should be able to add and remove trusted verifiers + owner := authManager.Authorizable.Owner() + addr := "g1verifier_test" + + added := authManager.AddTrustedVerifier(addr, owner) + if !added { + t.Fatal("Owner should be able to add trusted verifier") + } + + if !authManager.IsTrustedVerifier(addr) { + t.Fatal("Added address should be trusted") + } + + removed := authManager.RemoveTrustedVerifier(addr, owner) + if !removed { + t.Fatal("Owner should be able to remove trusted verifier") + } + + if authManager.IsTrustedVerifier(addr) { + t.Fatal("Removed address should not be trusted") + } +} + +func TestTrustedVerifierNonOwnerRejected(t *testing.T) { + owner := authManager.Authorizable.Owner() + nonOwner := address("g1notowner") + addr := "g1verifier_test2" + + added := authManager.AddTrustedVerifier(addr, nonOwner) + if added { + t.Error("Non-owner should not be able to add trusted verifier") + } + + // Ensure owner can add, then non-owner cannot remove + addedOwner := authManager.AddTrustedVerifier(addr, owner) + if !addedOwner { + t.Fatal("Owner should be able to add trusted verifier") + } + + removedByNonOwner := authManager.RemoveTrustedVerifier(addr, nonOwner) + if removedByNonOwner { + t.Error("Non-owner should not be able to remove trusted verifier") + } + + // Clean up + authManager.RemoveTrustedVerifier(addr, owner) +} From 6cd9afc1f128a2f2254c5600b356efe03a6a0d9a Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Mon, 17 Nov 2025 09:50:53 +0100 Subject: [PATCH 21/25] refactor: enforce owner-only access checks in AddTrustedVerifier and RemoveTrustedVerifier methods --- packages/r/karma1337/geo-resto/auth.gno | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/r/karma1337/geo-resto/auth.gno b/packages/r/karma1337/geo-resto/auth.gno index 1e02e93..95958a3 100644 --- a/packages/r/karma1337/geo-resto/auth.gno +++ b/packages/r/karma1337/geo-resto/auth.gno @@ -40,9 +40,12 @@ func (am *AuthManager) IsTrustedVerifier(addr string) bool { // AddTrustedVerifier allows the owner to add an address as a trusted verifier func (am *AuthManager) AddTrustedVerifier(addr string, caller address) bool { - // Try to add via the external authorizable extension. That extension - // enforces owner-only access internally; we call it and mirror the - // change locally so we can query membership efficiently. + // Owner-only check + if am.Authorizable.Owner() != caller { + return false + } + + // Add via the authorizable extension and mirror locally. err := am.Authorizable.AddToAuthList(address(addr)) if err != nil { return false @@ -53,6 +56,11 @@ func (am *AuthManager) AddTrustedVerifier(addr string, caller address) bool { // RemoveTrustedVerifier allows the owner to remove a trusted verifier func (am *AuthManager) RemoveTrustedVerifier(addr string, caller address) bool { + // Owner-only check + if am.Authorizable.Owner() != caller { + return false + } + err := am.Authorizable.DeleteFromAuthList(address(addr)) if err != nil { return false From 865cb24b18a5bb0a82ce664291254d06dd7e0ab4 Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Mon, 17 Nov 2025 09:50:58 +0100 Subject: [PATCH 22/25] refactor: add owner-only actions for managing trusted verifiers in renderer --- packages/r/karma1337/geo-resto/renderer.gno | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/r/karma1337/geo-resto/renderer.gno b/packages/r/karma1337/geo-resto/renderer.gno index 4458a35..c1f8d0b 100644 --- a/packages/r/karma1337/geo-resto/renderer.gno +++ b/packages/r/karma1337/geo-resto/renderer.gno @@ -4,6 +4,8 @@ import ( "strconv" "strings" "time" + + "chain/runtime" ) // Renderer handles the display and formatting of geographic data for web interface @@ -48,6 +50,16 @@ func (r *Renderer) RenderMainPage() string { createEventGenericLink := GetCreateEventTxLink("", "", "", 0, 0, 0) sb.WriteString("- [🎯 Create Event](" + createEventGenericLink + ")\n\n") + // Owner-only actions: add/remove trusted verifiers + // Use runtime.PreviousRealm() to detect the current caller for the web UI. + caller := runtime.PreviousRealm().Address() + if authManager.IsAdmin(caller.String()) { + addTrusted := GetAddTrustedVerifierTxLink("") + removeTrusted := GetRemoveTrustedVerifierTxLink("") + sb.WriteString("- [πŸ” Add Trusted Verifier](" + addTrusted + ") (owner only)\n\n") + sb.WriteString("- [πŸ”“ Remove Trusted Verifier](" + removeTrusted + ") (owner only)\n\n") + } + // Recent activity sb.WriteString("## πŸ”₯ Recent Activity\n") recentVisits := visitManager.GetRecentVisits(5) @@ -308,6 +320,15 @@ func (r *Renderer) RenderEvent(eventID string) string { verifyLink := GetVerifyPresenceTxLink(event.ID, "", "") sb.WriteString("- [βœ… Verify Attendee (organizer)](" + verifyLink + ")\n\n") + // Owner-only: add/remove trusted verifier links (prefill target addr with owner as a convenience) + caller := runtime.PreviousRealm().Address() + if authManager.IsAdmin(caller.String()) { + addTrusted := GetAddTrustedVerifierTxLink(caller.String()) + removeTrusted := GetRemoveTrustedVerifierTxLink(caller.String()) + sb.WriteString("- [πŸ” Add Trusted Verifier](" + addTrusted + ") (owner only)\n\n") + sb.WriteString("- [πŸ”“ Remove Trusted Verifier](" + removeTrusted + ") (owner only)\n\n") + } + // Verification Statistics totalParticipants := len(event.Participants) verifiedCount := len(event.VerifiedAttendees) From 7981ebd1aff5437b9d239836c1d9f88809f075e8 Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Wed, 19 Nov 2025 09:58:04 +0100 Subject: [PATCH 23/25] fix: fixing the coding style --- .../geo-resto/auth_trusted_verifier_test.gno | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/packages/r/karma1337/geo-resto/auth_trusted_verifier_test.gno b/packages/r/karma1337/geo-resto/auth_trusted_verifier_test.gno index 4860422..2451ab9 100644 --- a/packages/r/karma1337/geo-resto/auth_trusted_verifier_test.gno +++ b/packages/r/karma1337/geo-resto/auth_trusted_verifier_test.gno @@ -5,50 +5,50 @@ import ( ) func TestTrustedVerifierOwnerFlow(t *testing.T) { - // Owner should be able to add and remove trusted verifiers - owner := authManager.Authorizable.Owner() - addr := "g1verifier_test" - - added := authManager.AddTrustedVerifier(addr, owner) - if !added { - t.Fatal("Owner should be able to add trusted verifier") - } - - if !authManager.IsTrustedVerifier(addr) { - t.Fatal("Added address should be trusted") - } - - removed := authManager.RemoveTrustedVerifier(addr, owner) - if !removed { - t.Fatal("Owner should be able to remove trusted verifier") - } - - if authManager.IsTrustedVerifier(addr) { - t.Fatal("Removed address should not be trusted") - } + // Owner should be able to add and remove trusted verifiers + owner := authManager.Authorizable.Owner() + addr := "g1verifier_test" + + added := authManager.AddTrustedVerifier(addr, owner) + if !added { + t.Fatal("Owner should be able to add trusted verifier") + } + + if !authManager.IsTrustedVerifier(addr) { + t.Fatal("Added address should be trusted") + } + + removed := authManager.RemoveTrustedVerifier(addr, owner) + if !removed { + t.Fatal("Owner should be able to remove trusted verifier") + } + + if authManager.IsTrustedVerifier(addr) { + t.Fatal("Removed address should not be trusted") + } } func TestTrustedVerifierNonOwnerRejected(t *testing.T) { - owner := authManager.Authorizable.Owner() - nonOwner := address("g1notowner") - addr := "g1verifier_test2" - - added := authManager.AddTrustedVerifier(addr, nonOwner) - if added { - t.Error("Non-owner should not be able to add trusted verifier") - } - - // Ensure owner can add, then non-owner cannot remove - addedOwner := authManager.AddTrustedVerifier(addr, owner) - if !addedOwner { - t.Fatal("Owner should be able to add trusted verifier") - } - - removedByNonOwner := authManager.RemoveTrustedVerifier(addr, nonOwner) - if removedByNonOwner { - t.Error("Non-owner should not be able to remove trusted verifier") - } - - // Clean up - authManager.RemoveTrustedVerifier(addr, owner) + owner := authManager.Authorizable.Owner() + nonOwner := address("g1notowner") + addr := "g1verifier_test2" + + added := authManager.AddTrustedVerifier(addr, nonOwner) + if added { + t.Error("Non-owner should not be able to add trusted verifier") + } + + // Ensure owner can add, then non-owner cannot remove + addedOwner := authManager.AddTrustedVerifier(addr, owner) + if !addedOwner { + t.Fatal("Owner should be able to add trusted verifier") + } + + removedByNonOwner := authManager.RemoveTrustedVerifier(addr, nonOwner) + if removedByNonOwner { + t.Error("Non-owner should not be able to remove trusted verifier") + } + + // Clean up + authManager.RemoveTrustedVerifier(addr, owner) } From 0175a2df9b6f5c08984d6f93750f56c0ff713fc9 Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Wed, 19 Nov 2025 11:33:27 +0100 Subject: [PATCH 24/25] refactor: remove obsolete 'zkproof is hard' documentation file --- .../r/karma1337/geo-resto/zkproof is hard.md | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 packages/r/karma1337/geo-resto/zkproof is hard.md diff --git a/packages/r/karma1337/geo-resto/zkproof is hard.md b/packages/r/karma1337/geo-resto/zkproof is hard.md deleted file mode 100644 index 287b514..0000000 --- a/packages/r/karma1337/geo-resto/zkproof is hard.md +++ /dev/null @@ -1,16 +0,0 @@ -zkproof is hard - -online event maybe - -dynamic qrcode - -otp auth to proof - -create a transaction link to have an adena popup and do action like create event, register presence, etc etc - -https://github.com/samouraiworld/zenao - -https://gno.land/r/docs/txlink - - -resolve address and retrieve username from address https://gno.land/r/docs/resolveusers \ No newline at end of file From 92d93030f96c69764a07790c530c792f8511dfed Mon Sep 17 00:00:00 2001 From: Luca Grange Date: Wed, 26 Nov 2025 03:43:24 +0100 Subject: [PATCH 25/25] refactor: replace address truncation with name resolution for user visibility --- packages/r/karma1337/geo-resto/renderer.gno | 32 +++++++++++++++------ 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/r/karma1337/geo-resto/renderer.gno b/packages/r/karma1337/geo-resto/renderer.gno index c1f8d0b..a370384 100644 --- a/packages/r/karma1337/geo-resto/renderer.gno +++ b/packages/r/karma1337/geo-resto/renderer.gno @@ -6,6 +6,8 @@ import ( "time" "chain/runtime" + + "gno.land/r/sys/users" ) // Renderer handles the display and formatting of geographic data for web interface @@ -69,7 +71,7 @@ func (r *Renderer) RenderMainPage() string { for _, visit := range recentVisits { location := locationManager.GetLocation(visit.LocationID) if location != nil { - sb.WriteString("- " + r.truncateAddress(visit.UserAddress) + " visited **" + location.Name + "** " + r.timeAgo(visit.Timestamp) + "\n") + sb.WriteString("- " + r.resolveName(visit.UserAddress) + " visited **" + location.Name + "** " + r.timeAgo(visit.Timestamp) + "\n") } } } @@ -125,7 +127,7 @@ func (r *Renderer) RenderLocation(locationID string) string { sb.WriteString("**Description**: " + location.Description + "\n") sb.WriteString("**Category**: " + location.Category + "\n") sb.WriteString("**Coordinates**: " + strconv.FormatFloat(location.Latitude, 'f', 6, 64) + ", " + strconv.FormatFloat(location.Longitude, 'f', 6, 64) + "\n") - sb.WriteString("**Created by**: " + r.truncateAddress(location.Creator.String()) + "\n") + sb.WriteString("**Created by**: " + r.resolveName(location.Creator.String()) + "\n") sb.WriteString("**Created**: " + r.formatTimestamp(location.CreatedAt) + "\n") sb.WriteString("**Total Visits**: " + strconv.Itoa(location.VisitCount) + "\n") if location.Verified { @@ -160,7 +162,7 @@ func (r *Renderer) RenderLocation(locationID string) string { for i := len(visits) - limit; i < len(visits); i++ { visit := visits[i] - sb.WriteString("- " + r.truncateAddress(visit.UserAddress) + " - " + r.formatTimestamp(visit.Timestamp)) + sb.WriteString("- " + r.resolveName(visit.UserAddress) + " - " + r.formatTimestamp(visit.Timestamp)) if visit.Verified { sb.WriteString(" βœ…") } @@ -184,7 +186,7 @@ func (r *Renderer) RenderLocation(locationID string) string { func (r *Renderer) RenderUserVisits(userAddress string) string { var sb strings.Builder - sb.WriteString("# πŸ‘€ User Visits: " + r.truncateAddress(userAddress) + "\n\n") + sb.WriteString("# πŸ‘€ User Visits: " + r.resolveName(userAddress) + "\n\n") visits := visitManager.GetUserVisits(userAddress) if len(visits) == 0 { @@ -230,7 +232,7 @@ func (r *Renderer) RenderRecentVisits() string { for _, visit := range visits { location := locationManager.GetLocation(visit.LocationID) if location != nil { - sb.WriteString("- **" + location.Name + "** visited by " + r.truncateAddress(visit.UserAddress) + " " + r.timeAgo(visit.Timestamp)) + sb.WriteString("- **" + location.Name + "** visited by " + r.resolveName(visit.UserAddress) + " " + r.timeAgo(visit.Timestamp)) if visit.Verified { sb.WriteString(" βœ…") } @@ -284,7 +286,7 @@ func (r *Renderer) RenderEvent(eventID string) string { } sb.WriteString("\n") - sb.WriteString("**Created by**: " + r.truncateAddress(event.Creator.String()) + "\n") + sb.WriteString("**Created by**: " + r.resolveName(event.Creator.String()) + "\n") sb.WriteString("**Start Time**: " + r.formatTimestamp(event.StartTime) + "\n") sb.WriteString("**End Time**: " + r.formatTimestamp(event.EndTime) + "\n") sb.WriteString("**Participants**: " + strconv.Itoa(len(event.Participants))) @@ -342,7 +344,7 @@ func (r *Renderer) RenderEvent(eventID string) string { if len(event.VerifiedAttendees) > 0 { sb.WriteString("### βœ… Verified Attendees\n\n") for _, attendee := range event.VerifiedAttendees { - sb.WriteString("- " + r.truncateAddress(attendee) + "\n") + sb.WriteString("- " + r.resolveName(attendee) + "\n") } sb.WriteString("\n") } @@ -353,7 +355,7 @@ func (r *Renderer) RenderEvent(eventID string) string { if len(event.Participants) > 0 { sb.WriteString("## πŸ‘₯ Participants\n\n") for i, participant := range event.Participants { - sb.WriteString(strconv.Itoa(i+1) + ". " + r.truncateAddress(participant) + "\n") + sb.WriteString(strconv.Itoa(i+1) + ". " + r.resolveName(participant) + "\n") } } @@ -431,6 +433,20 @@ func (r *Renderer) truncateAddress(address string) string { return address[:6] + "..." + address[len(address)-6:] } +func (r *Renderer) resolveName(addr string) string { + // Try to resolve a human-readable username for the given address. + data := users.ResolveAddress(address(addr)) + if data != nil { + name := data.Name() + if name != "" { + return name + } + } + // If no username is registered, return the full address so users can + // identify or copy it for registration. Previously we truncated here. + return addr +} + func (r *Renderer) formatTimestamp(timestamp int64) string { t := time.Unix(timestamp, 0) return t.Format("2006-01-02 15:04:05")