How the Geohash Subscription Manager Optimizes Filtering for Location Channels in BitChat

The GeohashSubscriptionManager in BitChat optimizes location channel filtering through targeted ephemeral filters, relay-proximity selection, and intelligent subscription deduplication to minimize bandwidth and eliminate redundant work.

BitChat's geohash-based location channels require efficient Nostr relay subscription management to handle real-time messaging without overwhelming devices or networks. The GeohashSubscriptionManager (implemented in bitchat/ViewModels/GeohashSubscriptionManager.swift) serves as the central coordinator for all geohash subscription logic, employing three complementary optimization techniques that work together to keep the UI responsive and resource usage minimal.

Targeted Ephemeral Filters for Temporal Narrowing

The first optimization technique involves time-bounded event filtering. Rather than fetching complete channel histories, the manager constructs ephemeral filters that request only recent messages.

In resubscribeCurrentGeohash() (lines 39-44), the manager builds a filter using NostrFilter.geohashEphemeral:

let filter = NostrFilter.geohashEphemeral(
    channel.geohash,
    since: Date().addingTimeInterval(-TransportConfig.nostrGeohashInitialLookbackSeconds),
    limit: TransportConfig.nostrGeohashInitialLimit
)

The since parameter creates a look-back window defined by TransportConfig.nostrGeohashInitialLookbackSeconds, while limit caps results via TransportConfig.nostrGeohashInitialLimit. This temporal narrowing prevents fetching months-old events that the UI never renders, directly reducing bandwidth consumption and parse overhead.

Relay-Proximity Selection for Spatial Optimization

The second technique addresses network topology efficiency. After constructing the filter, the manager selects relays physically close to the target geohash.

At lines 44-48 of the same function, the manager calls:

let relays = GeoRelayDirectory.shared.closestRelays(
    toGeohash: channel.geohash,
    count: 5
)

By defaulting to 5 nearest relays, this approach:

  • Reduces network latency through shorter routing paths
  • Avoids broadcasting to distant relays that would return duplicate data
  • Minimizes redundant relay connections for the same geohash region

The GeoRelayDirectory implementation (in bitchat/Nostr/GeoRelayDirectory.swift) maintains the mapping between geohash regions and optimal relay endpoints.

Deduplication and Sampling for Subscription Efficiency

The third technique involves subscription lifecycle management to prevent redundant filter creation. The manager tracks active geohash sampling subscriptions through two mechanisms:

Sampling Subscription Map

The geoSamplingSubs dictionary maintains a mapping of subscription IDs to geohashes. In beginGeohashSampling() (lines 36-44), the manager performs set arithmetic to:

  • Add only missing subscriptions: New geohashes in the desired set that aren't currently tracked
  • Remove only obsolete subscriptions: Previously tracked geohashes no longer needed
func beginGeohashSampling(for geohashes: [String]) {
    let desired = Set(geohashes)
    let current = Set(context.geoSamplingSubs.values)

    // Add new geohashes
    for gh in desired.subtracting(current) {
        subscribe(gh)  // creates "geo-sample-<gh>" sub
    }

    // Remove stale ones
    for (subID, gh) in context.geoSamplingSubs 
        where current.subtracting(desired).contains(gh) {
        NostrRelayManager.shared.unsubscribe(id: subID)
        context.removeGeoSamplingSub(subID)
    }
}

Gateway Notice Deduplication

The gatewayNoticeGeohashes set (lines 18-21) tracks which geohashes have already displayed the "sent via mesh gateway" system message. This prevents emitting duplicate notices per outgoing message, reducing UI churn and user distraction.

Complete Channel Switch Flow

When a user switches location channels via switchLocationChannel(to:) (lines 35-50), all three optimizations execute in sequence:

func switchLocationChannel(to channel: ChannelID) {
    // 1. Flush pending UI messages
    context.flushPublicMessagePipeline()
    
    // 2. Unsubscribe existing geo subscriptions
    if let sub = context.geoSubscriptionID {
        NostrRelayManager.shared.unsubscribe(id: sub)
        context.setGeoChatSubscriptionID(nil)
    }
    
    // 3. Validate and extract geohash
    guard case .location(let geoChannel) = channel else { return }
    context.currentGeohash = geoChannel.geohash
    
    // 4. Build time-bounded ephemeral filter
    let filter = NostrFilter.geohashEphemeral(
        geoChannel.geohash,
        since: Date().addingTimeInterval(-TransportConfig.nostrGeohashInitialLookbackSeconds),
        limit: TransportConfig.nostrGeohashInitialLimit
    )
    
    // 5. Select closest relays
    let relays = GeoRelayDirectory.shared.closestRelays(
        toGeohash: geoChannel.geohash,
        count: 5
    )
    
    // 6. Subscribe with deterministic ID
    NostrRelayManager.shared.subscribe(
        filter: filter,
        id: "geo-\(geoChannel.geohash)",
        relayUrls: relays
    ) { [weak self] event in
        self?.inbound.handleNostrEvent(event)
    }
    
    // 7. Start participant refresh timer
    startParticipantRefreshTimer()
}

The deterministic subscription ID ("geo-\(geohash)") enables reliable subscription tracking and prevents duplicate subscriptions for the same geohash.

Configuration Constants

The optimization behavior is tunable through TransportConfig (bitchat/Transport/TransportConfig.swift):

Constant Purpose
nostrGeohashInitialLookbackSeconds Defines how far back to fetch events when entering a channel
nostrGeohashInitialLimit Maximum events to request in initial subscription

These values balance between immediate content availability and resource efficiency.

Key Source Files

Understanding the complete filtering optimization requires examining these files in the permissionlesstech/bitchat repository:

Summary

  • Temporal filtering via NostrFilter.geohashEphemeral with configurable look-back windows prevents fetching stale events
  • Spatial optimization through GeoRelayDirectory.closestRelays reduces latency and eliminates duplicate data from distant relays
  • Subscription deduplication via set-based sampling management adds only missing and removes only obsolete subscriptions
  • Message-level deduplication through gatewayNoticeGeohashes prevents redundant system notifications
  • Deterministic subscription IDs (geo-<geohash>) enable reliable subscription lifecycle tracking

Frequently Asked Questions

What is the default number of relays used for geohash subscriptions?

BitChat defaults to 5 nearest relays as specified in GeoRelayDirectory.shared.closestRelays(toGeohash:count:) calls within GeohashSubscriptionManager. This count balances redundancy against connection overhead, though the implementation supports arbitrary counts through the count parameter.

How does BitChat prevent fetching old messages when joining a location channel?

The manager applies temporal narrowing through TransportConfig.nostrGeohashInitialLookbackSeconds, creating a since timestamp for the ephemeral filter. Only events newer than this threshold are requested, avoiding bandwidth waste on historical messages the UI doesn't display.

What prevents duplicate subscriptions when geohashes change?

The beginGeohashSampling() method performs set difference operations between desired and current geohash sets. It subscribes only to newly added geohashes and unsubscribes only from removed ones, maintaining stable subscriptions for unchanged regions through the geoSamplingSubs tracking dictionary.

Where does the "sent via mesh gateway" notice deduplication occur?

The gatewayNoticeGeohashes set in GeohashSubscriptionManager (lines 18-21) tracks which geohashes have already shown this system message. Before emitting the notice, the manager checks membership in this set, ensuring users see the notification exactly once per channel rather than per message.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →