# How Bitchat Handles Seamless Network Transitions Between Bluetooth and Nostr

> Discover how Bitchat achieves seamless network transitions between Bluetooth and Nostr by treating them as interchangeable transports, ensuring continuous peer state and automatic fallback.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: how-to-guide
- Published: 2026-08-09

---

**Bitchat treats Bluetooth-LE mesh and Nostr relay communication as interchangeable Transport implementations, automatically falling back to Nostr when Bluetooth becomes unavailable while maintaining a unified peer state across both networks.**

The permissionlesstech/bitchat repository implements a robust dual-network architecture that enables seamless network transitions between Bluetooth and Nostr without user intervention. By abstracting both communication methods behind a unified `Transport` protocol, the application continuously monitors connectivity state and routes messages through the most reliable available channel.

## Transport Abstraction Architecture

### The Transport Protocol Contract

Both `BluetoothTransport` and `NostrTransport` implement the same `Transport` protocol, exposing identical methods for sending, receiving, and discovering peers. This abstraction ensures that higher-level components remain agnostic to the underlying network technology. Both concrete classes implement shared delegate protocols including `TransportPeerEventsDelegate` and `SynchronousMessageTransportEventDelegate`, allowing [`ChatViewModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatViewModel.swift) to handle events uniformly regardless of source.

### BluetoothTransport Mesh Implementation

`BluetoothTransport` utilizes CoreBluetooth to form an ad-hoc mesh network. It broadcasts messages to physically proximate devices and reports peer snapshots through the [`MeshTransportCapabilities.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MeshTransportCapabilities.swift) helper methods. The transport exposes whether the mesh is attached and whether Bluetooth is usable through capability checks that feed into the unified peer service.

### NostrTransport Relay Implementation

`NostrTransport` conforms to the `Transport` protocol while routing messages through configurable Nostr relays. As implemented in [`NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrTransport.swift), the class optionally tunnels connections via Tor and maintains persistent relay connections through periodic reachability checks. It utilizes `NostrIdentityBridge` to sign events with the local Nostr identity before publishing.

## Unified Peer State Management

### The UnifiedPeerService Architecture

At the core of the transition logic sits [`UnifiedPeerService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/UnifiedPeerService.swift), which acts as the single source of truth for peer availability across both networks. The service aggregates peer snapshots from the Bluetooth mesh and incorporates Nostr "favorites" (offline peers reachable only via relay) through the `updatePeers()` method:

```swift
private func updatePeers() {
    let meshPeers = meshService.currentPeerSnapshots()
    let hasAnyConnected = meshPeers.contains { $0.isConnected }
    // Phase 1 – add all mesh peers (connected + reachable)
    // Phase 2 – add offline favorites that are reachable via Nostr
}

```

### Peer Reachability Classification

The service distinguishes between `isConnected` (active Bluetooth mesh participants) and `isReachable` (peers available through Nostr). When `hasAnyConnected` evaluates to false, indicating no active Bluetooth peers, the system automatically executes Phase 2 logic, promoting Nostr-identified peers from the `favorites` dictionary to active status. This ranking logic prioritizes `isConnected` over `isReachable` to ensure low-latency local delivery when available.

## Bluetooth State Monitoring and Event Handling

### CoreBluetooth State Observation

[`ChatViewModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatViewModel.swift) registers for Bluetooth state changes through the `CBCentralManager` delegate pattern. When CoreBluetooth reports state changes, the view model triggers UI updates and informs the peer service:

```swift
func didUpdateBluetoothState(_ state: CBManagerState) {
    updateBluetoothState(state)
}

private func updateBluetoothState(_ state: CBManagerState) {
    let alertUpdate = ChatBluetoothAlertPolicy.update(for: state)
    showBluetoothAlert = alertUpdate.isPresented
}

```

### Alert Policy Integration

The [`ChatBluetoothAlertPolicy.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatBluetoothAlertPolicy.swift) utility maps `CBManagerState` values—including `.poweredOff`, `.unauthorized`, and `.unsupported`—to UI presentation logic. When Bluetooth becomes unavailable, `showBluetoothAlert` triggers, prompting the UI to dismiss Bluetooth-dependent modals and prepare for Nostr-only operation.

## Automatic Network Fallback Mechanism

The hand-off between networks occurs automatically through a five-phase sequence managed by the transport layer:

| Step | What Happens | Code Reference |
|------|--------------|----------------|
| **Bluetooth becomes unavailable** | CoreBluetooth delegate fires `didUpdateBluetoothState(.poweredOff)`. `ChatViewModel` updates `showBluetoothAlert`. | `ChatViewModel.updateBluetoothState` |
| **Mesh snapshots stop updating** | `UnifiedPeerService` receives an empty list from `meshService.currentPeerSnapshots()`. `hasAnyConnected` becomes `false`. | `UnifiedPeerService.updatePeers` |
| **Fallback to Nostr** | `UnifiedPeerService` adds offline favorites (built from Nostr identities) in Phase 2, marking them `isReachable`. | `UnifiedPeerService.updatePeers` |
| **Message sending** | The UI invokes `Transport.send()`. With BLE unavailable, the implementation routes through `NostrTransport`. | `NostrTransport.send` |
| **Arrival** | Recipient devices apply the same peer-ranking logic (`isConnected > isReachable`) to display the message. | `UnifiedPeerService` ranking logic |

## NostrTransport Implementation Details

### Event Signing and Relay Publishing

The `send(event:)` method in [`NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrTransport.swift) handles message encryption and signing via `NostrIdentityBridge` before publishing to configured relays:

```swift
final class NostrTransport: Transport, @unchecked Sendable {
    func send(event: NostrEvent) async throws {
        // encrypt, sign, and publish via the selected relay
    }
}

```

### Reachability Maintenance

`scheduleReachabilityChecks()` performs periodic health checks to ensure the Nostr connection remains viable for immediate fallback. This background task maintains the relay connection pool, ensuring zero-latency availability when the Bluetooth mesh drops.

## Summary

- **Transport Abstraction**: Both Bluetooth and Nostr implement the identical `Transport` protocol, enabling interchangeable usage throughout the codebase.
- **UnifiedPeerService**: Maintains a consolidated view of peer availability, automatically promoting Nostr favorites when Bluetooth peers disconnect.
- **State Monitoring**: `ChatViewModel` observes `CBManagerState` changes and triggers UI alerts while the service layer handles backend fallback.
- **Automatic Routing**: The system prefers low-latency Bluetooth mesh when available and seamlessly transitions to Nostr relays without user action.
- **Implementation Files**: Key logic resides in [`UnifiedPeerService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/UnifiedPeerService.swift), [`ChatViewModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatViewModel.swift), [`NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrTransport.swift), and [`ChatBluetoothAlertPolicy.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatBluetoothAlertPolicy.swift).

## Frequently Asked Questions

### How does Bitchat decide which network to use for sending a message?

Bitchat always attempts to use the Bluetooth mesh first when peers are available. `UnifiedPeerService` checks `meshService.currentPeerSnapshots()` for any peers where `isConnected` is true. If none exist, the service automatically falls back to Nostr by including offline favorites in the active peer list, causing messages to route through `NostrTransport` instead.

### Does the user need to manually switch between Bluetooth and Nostr modes?

No. The transition is fully automatic. When `ChatViewModel` detects a Bluetooth state change to `.poweredOff` or `.unauthorized` via `didUpdateBluetoothState(_:)`, it updates UI alerts while `UnifiedPeerService` simultaneously adjusts peer availability. The message sending interface continues to function using the same `Transport` methods, with the underlying implementation handling the network selection.

### What happens to messages if both Bluetooth and Nostr are unavailable?

If no Bluetooth peers are connected and Nostr relay connectivity fails, the `Transport` implementation will throw an error through the `send(event:)` method's `throws` clause. The application maintains a queue of unsent messages and retries when either transport reports restored connectivity through its respective reachability checks.

### Is Nostr communication in Bitchat always routed through Tor?

Tor usage is optional but supported. `NostrTransport` can be configured to route relay connections through Tor for enhanced privacy. The transport performs reachability checks via `scheduleReachabilityChecks()` to ensure the connection remains stable, regardless of whether it uses direct TCP or Tor proxying.