How Bitchat Handles Seamless Network Transitions Between Bluetooth and Nostr

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 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 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, 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, 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:

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 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:

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 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 handles message encryption and signing via NostrIdentityBridge before publishing to configured relays:

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, ChatViewModel.swift, NostrTransport.swift, and 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.

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 →