How BitChat Implements Geographic Chat Rooms (Location Channels)
BitChat implements geographic chat rooms using hierarchical geohash strings derived from GPS coordinates, where LocationStateManager generates precision-scoped channels and GeohashPresenceService broadcasts anonymized presence heartbeats to Nostr relays for low-precision levels only.
BitChat's Location Channels enable users to join chat rooms scoped to their physical location without exposing precise coordinates. According to the permissionlesstech/bitchat source code, the implementation relies on geohash encoding to map latitude and longitude into hierarchical string identifiers, creating a dual-transport model where mesh chats remain local while location-based messages route through the global Nostr network.
Core Architecture Components
GeohashChannelLevel and GeohashChannel
In bitchat/Protocols/LocationChannel.swift, the GeohashChannelLevel enum defines five precision levels ranging from region (2 characters) to building (8 characters). The GeohashChannel struct holds the computed geohash string and its corresponding level, enabling type-safe channel identification throughout the codebase.
LocationStateManager
The LocationStateManager class in bitchat/Services/LocationStateManager.swift serves as the central coordinator. It uses CoreLocation to obtain one-shot GPS fixes, then generates a full set of GeohashChannel instances via Geohash.encode(latitude:longitude:precision:). This produces varying precision strings: length 2 for region, 4 for province, 6 for city, and 8 for building. The manager exposes availableChannels and selectedChannel as published properties for UI observation.
GeohashPresenceService
Located in bitchat/Services/GeohashPresenceService.swift, this service handles decentralized presence broadcasting. Every 40-80 seconds, it generates Kind 20001 Nostr events for low-precision geohash channels (region, province, city) while explicitly excluding neighborhood, block, and building levels to prevent fine-grained location leakage.
How Geographic Chat Rooms Work
-
Location Acquisition – When users enable location channels,
LocationStateManager.enableLocationChannels()requests CoreLocation permissions and triggers a one-shot location request. -
Geohash Generation – In
LocationStateManager.computeChannels(from:), the system iterates throughGeohashChannelLevel.allCases, callingGeohash.encode(latitude:longitude:precision:)for each level to produce strings of appropriate length. The resulting array populatesavailableChannels. -
Channel Selection – UI components like
LocationChannelsSheetinvokeLocationStateManager.select(_:)with aChannelID.location(GeohashChannel)parameter. The manager persists the selection and sets theteleportedflag when users manually select a geohash outside their current GPS location. -
Presence Broadcasting –
GeohashPresenceServicemonitorsavailableChannelsand schedules heartbeat tasks every 40-80 seconds. Each task applies a random 2-5 second delay, derives a temporary Nostr identity viaNostrIdentityBridge.deriveIdentity(forGeohash:), constructs a Kind 20001 event usingNostrProtocol.createGeohashPresenceEvent, and publishes to relays returned byGeoRelayDirectory.closestRelays(toGeohash:count:). -
Privacy Enforcement – The service validates precision through
allowedPrecisions.contains(channel.geohash.count), ensuring only region, province, and city-level geohashes (lengths 2, 4, and 6) receive presence broadcasts while higher-precision levels remain local-only. -
Reverse Geocoding –
LocationStateManagerusesCLGeocoderto convert coordinates into human-readable names (e.g., "San Francisco • u4pruy"), storing these inlocationNamesfor UI display and bookmark resolution.
Implementation Code Examples
// Create a location channel for the current city level
let cityChannel = GeohashChannel(level: .city,
geohash: "u4pruy") // 6-character city-level geohash
// Switch UI to that channel
LocationStateManager.shared.select(.location(cityChannel))
// Manually trigger a presence heartbeat for debugging
await GeohashPresenceService.shared.performHeartbeat()
// Add a persistent geohash bookmark
LocationStateManager.shared.addBookmark("9q8yy")
Key Source Files
bitchat/Protocols/LocationChannel.swift– DefinesGeohashChannelLevel,GeohashChannel, andChannelIDprotocols.bitchat/Services/LocationStateManager.swift– Core manager converting GPS to geohash channels, handling persistence and bookmarks.bitchat/Services/GeohashPresenceService.swift– Broadcasts presence heartbeats to Nostr relays with privacy controls.bitchat/Views/LocationChannelsSheet.swift– UI component for channel selection.bitchat/Views/GeohashPeopleList.swift– Displays participants within a specific geohash channel.
Summary
- BitChat uses geohash encoding to convert GPS coordinates into hierarchical string identifiers representing geographic areas from region to building precision.
- The
LocationStateManagergenerates multiple precision levels simultaneously, allowing users to select granularity from coarse (regional) to fine (building-level) chat rooms. - Privacy protection occurs at the network layer:
GeohashPresenceServicebroadcasts presence only for low-precision geohashes (2-6 characters) to Nostr relays, keeping precise location data local. - The dual-transport model routes mesh-only conversations locally while propagating location-based discovery through the global Nostr relay network using Kind 20001 events.
- Users can teleport to arbitrary geohashes or bookmark specific locations for persistent access without physical presence.
Frequently Asked Questions
What geohash precision levels does BitChat support?
BitChat implements five hierarchical levels defined in GeohashChannelLevel: region (2 characters), province (4 characters), city (6 characters), neighborhood (7 characters), block (7+ characters), and building (8 characters). The UI in LocationChannelsSheet presents these as nested geographic scopes for user selection.
How does BitChat prevent precise location leaks over Nostr?
The GeohashPresenceService explicitly filters broadcasts using allowedPrecisions.contains(channel.geohash.count), restricting presence heartbeats to geohashes of length 2, 4, or 6 (region through city). Higher-precision levels representing neighborhoods, blocks, and buildings never generate Kind 20001 events, ensuring fine-grained location data remains confined to local mesh transmission.
Can users join location channels without sharing their GPS location?
Yes. The teleported flag in LocationStateManager indicates when a user selects a geohash channel not matching their current GPS coordinates. Users can manually enter geohash strings or select from bookmarks using LocationStateManager.shared.addBookmark(), enabling participation in distant geographic chat rooms without physical presence or revealing their actual location.
How often does BitChat broadcast presence heartbeats?
GeohashPresenceService schedules presence broadcasts every 40-80 seconds with a random 2-5 second jitter delay per channel. This decorrelation prevents traffic spikes while maintaining sufficient presence granularity for the Nostr relay network to route messages to active geographic cells.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →