# How Nostr Relays Are Selected and Managed in Bitchat Android Messaging

> Discover how Bitchat Android selects and manages Nostr relays using geohash proximity, default pools, and advanced reconnection strategies for efficient and private messaging.

- Repository: [permissionlesstech/bitchat-android](https://github.com/permissionlesstech/bitchat-android)
- Tags: internals
- Published: 2026-08-04

---

**Bitchat Android uses a dedicated `NostrRelayManager` class that combines a static default relay pool with geohash‑based proximity selection, backed by exponential back‑off reconnection and live‑location privacy gating.**

The Bitchat Android app implements a sophisticated, multi‑layered approach to Nostr relay handling. At its core, the `NostrRelayManager` in [`app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt) orchestrates relay selection, connection lifecycle, and subscription management—ensuring messages reach the right relays while respecting user privacy.

## Default Relay Pool Initialization

When `NostrRelayManager` is instantiated, it immediately populates an internal `relaysList` with four hard‑coded public relays. These are defined in the `DEFAULT_RELAYS` constant at lines 44‑49:

- `wss://relay.damus.io`
- `wss://relay.primal.net`
- `wss://offchain.pub`
- `wss://nostr21.com`

These URLs are always kept in the `nonLiveRelayUrls` set, making them the fallback for all non‑live‑location traffic. This ensures baseline connectivity even when geohash‑specific selection fails or is disabled.

## Geohash‑Based Relay Selection

For location‑aware messaging, Bitchat Android selects relays based on geographic proximity using **geohash coordinates**.

### How Geohash Selection Works

1. **Directory lookup** – The `RelayDirectory` class queries `assets/nostr_relays.csv` (a CSV mapping relay hostnames to geohash coordinates) to find the *n* nearest relays for a given geohash via `closestRelaysForGeohash(geohash, nRelays)`.

2. **Caching** – Results are stored in `geohashToRelays[geohash]` to avoid repeated lookups.

3. **Default merging** – When `includeDefaults` is true, the nearest relays are unioned with the default pool: `(nearest + defaultRelays()).toSet()`.

The key method implementing this logic is `ensureGeohashRelaysConnected` (lines 45‑63 in [`NostrRelayManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/NostrRelayManager.kt)), which guarantees WebSocket connections exist before any message or subscription is attempted.

```kotlin
// Connect to 5 nearest relays for a geohash, plus defaults
NostrRelayManager
    .getInstance(context)
    .ensureGeohashRelaysConnected(
        geohash = "u4pruydqqvj",
        nRelays = 5,
        includeDefaults = true,
        liveLocationToken = token
    )

```

### Public API for Geohash Operations

| Method | Purpose |
|--------|---------|
| `ensureGeohashRelaysConnected(...)` | Forces connections for selected relays |
| `getRelaysForGeohash(...)` | Returns current relay list for a geohash |
| `sendEventToGeohash(event, geohash, ...)` | Sends event to geohash‑specific relays (with default fallback) |
| `subscribeForGeohash(...)` | Creates filtered subscription tied to a geohash |

## Connection and Subscription Lifecycle

### WebSocket Management

The manager creates OkHttp WebSocket connections on demand via `connectToRelay(url, liveLocationToken)`. Reconnection jobs are tracked in `reconnectJobs` to prevent duplicate attempts.

### Exponential Back‑Off Reconnection

When a non‑DNS disconnection occurs, `handleCurrentDisconnection` triggers exponential back‑off:

- Base interval: `INITIAL_BACKOFF_INTERVAL`
- Multiplier: `BACKOFF_MULTIPLIER` per attempt
- Maximum cap: `MAX_BACKOFF_INTERVAL`

The retry delay is calculated as `INITIAL_BACKOFF_INTERVAL * BACKOFF_MULTIPLIER^(attempt‑1)`. This logic appears around lines 335‑377 in [`NostrRelayManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/NostrRelayManager.kt).

### Subscription Persistence

After reconnection, `restoreSubscriptionsForRelay` automatically rebuilds all active subscriptions for that relay. Subscriptions are registered as `SubscriptionInfo` objects that record their originating geohash, ensuring they target the correct relay set (`targetRelayUrls = relayUrls`).

## Privacy and Security Controls

### Live Location Privacy Gating

Every network action checks `LiveLocationPrivacyGate.accepts(token)`. If a token is revoked, `revokeLiveLocationAccess` drops all live‑location‑specific connections and clears related state. This prevents network leakage that could deanonymize users.

### Event Deduplication and Queueing

Both sending and subscribing share:
- **`NostrPendingEventQueue`** – Buffers events before transmission
- **`NostrEventDeduplicator`** – Prevents duplicate processing

## Complete Usage Examples

```kotlin
// Initialize and connect to all relays (default + cached geohash)
val manager = NostrRelayManager.getInstance(context)
manager.connect()

// Send a chat message to geohash-specific relays
val dmEvent = NostrEvent.create(
    kind = 4,
    content = encryptedContent,
    tags = listOf(listOf("p", recipientPubkey))
)

manager.sendEventToGeohash(
    event = dmEvent,
    geohash = "u4pruydqqvj",
    includeDefaults = true,
    nRelays = 5
)

// Subscribe to messages in a geohash region
val chatFilter = NostrFilter.Tags(listOf("chat"))

manager.subscribeForGeohash(
    geohash = "u4pruydqqvj",
    filter = chatFilter,
    includeDefaults = true,
    nRelays = 5,
    handler = { event ->
        Log.d("Bitchat", "New message: ${event.content}")
    }
)

```

## Key Source Files

| File | Role |
|------|------|
| [`NostrRelayManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/NostrRelayManager.kt) | Core manager: default relays, geohash selection, connections, subscriptions, reconnection logic |
| [`RelayDirectory.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/RelayDirectory.kt) | Proximity-based relay lookup from `assets/nostr_relays.csv` |
| `assets/nostr_relays.csv` | Relay geolocation database for nearest‑neighbor queries |

## Summary

- **Default pool** of four well‑known relays provides always‑available fallback connectivity
- **Geohash-based selection** via `RelayDirectory` optimizes for geographic proximity, with optional default merging
- **Connection resilience** through exponential back‑off and automatic subscription restoration
- **Privacy enforcement** via `LiveLocationPrivacyGate` token validation before all network operations
- **Shared infrastructure** of pending event queues and deduplicators ensures reliable message flow

## Frequently Asked Questions

### How does Bitchat Android choose which Nostr relay to use for a message?

The `NostrRelayManager` first determines if the message is associated with a geohash. If so, it queries `RelayDirectory.closestRelaysForGeohash()` to find the nearest relays from `assets/nostr_relays.csv`, optionally merges the default relay pool, and sends to that combined set. For non‑geohash messages, it uses the static default pool (`wss://relay.damus.io`, `wss://relay.primal.net`, `wss://offchain.pub`, `wss://nostr21.com`).

### What happens if a Nostr relay connection drops in Bitchat Android?

The manager detects disconnections in `handleCurrentDisconnection`, increments `relay.reconnectAttempts`, and schedules a retry with exponential back‑off. Once reconnected, `restoreSubscriptionsForRelay` automatically resubscribes to all active filters for that relay. DNS failures are treated differently from transient errors to avoid unnecessary back‑off.

### Can users disable geohash-based relay selection?

While the public API exposes `includeDefaults` to force fallback to the default pool, the underlying geohash lookup in `RelayDirectory` can be bypassed by calling methods without a geohash parameter or by setting `nRelays` such that only defaults are used. The privacy gate also allows runtime revocation of live‑location access, which drops geohash‑specific connections.

### Where is the relay geolocation data stored?

Relay coordinates are stored in `app/src/main/assets/nostr_relays.csv`, a static CSV file bundled with the app. [`RelayDirectory.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/RelayDirectory.kt) parses this file at runtime to compute nearest‑neighbor lookups for any given geohash.