# How BitChat's Dual Transport Architecture Routes Messages Between Bluetooth Mesh and Nostr

> Discover how BitChat's dual transport architecture routes messages via Bluetooth mesh and Nostr. Learn about its smart fallback mechanism for seamless communication.

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

---

**BitChat routes messages through Bluetooth mesh first when peers are locally reachable, and automatically falls back to Nostr relays only when the mesh cannot reach the destination.**

BitChat implements a unique dual-transport architecture that combines Bluetooth Low Energy mesh networking with the Nostr protocol to enable censorship-resistant messaging. This architecture intelligently selects between local peer-to-peer radio transmission and Internet-based relay networks based on real-time reachability detection. Understanding how BitChat's dual transport architecture handles message routing reveals why the app prioritizes low-latency local connections while maintaining global connectivity through automatic Nostr fallback.

## Overview of the Two Transport Layers

BitChat ships with two independent transport implementations that serve different connectivity scenarios.

### Bluetooth Mesh (BLEService)

The **BLEService** provides a peer-to-peer radio mesh capable of directly reaching nearby devices without Internet infrastructure. This transport reports **reachability** when the Bluetooth stack observes a link or recent advertisement from the target peer. According to the source code in [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift) (line 1116), `isPeerReachable(_:)` returns `true` when the device has been seen within the mesh reachability window.

### Nostr Transport (NostrTransport)

The **NostrTransport** acts as an Internet-backed relay network. As implemented in [`bitchat/Services/NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NostrTransport.swift) (lines 216-231), this transport only reports reachability when two conditions are met: the mesh reports the peer as reachable *and* the client maintains an active WebSocket connection to at least one Nostr relay. This ensures Nostr serves strictly as a fallback mechanism rather than the default path.

## Message Routing Logic and Transport Selection

The core routing decision flow lives in [`MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessageRouter.swift). The architecture follows a priority-based selection process that evaluates transports in a specific order.

### Transport Priority Configuration

The bootstrapper establishes the preference order when initializing the chat view model. In [`bitchat/ViewModels/ChatViewModelBootstrapper.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/ChatViewModelBootstrapper.swift) (lines 31-34), the code creates the transport array with mesh listed first:

```swift
let nostrTransport = NostrTransport(keychain: keychain, idBridge: idBridge)
nostrTransport.senderPeerID = meshService.myPeerID
let transports: [Transport] = [meshService, nostrTransport]   // mesh first
let router = MessageRouter(transports: transports)

```

This array order is critical because `MessageRouter.reachableTransport(for:)` ([`MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessageRouter.swift) lines 82-84) returns the **first** transport whose `isPeerReachable(_:)` method returns `true`.

### Reachability Detection

Each transport implements reachability differently based on its underlying technology:

- **BLE Reachability**: `BLEService.isPeerReachable(_:)` checks if the Bluetooth stack currently sees the peer or if the peer was observed recently within the mesh window.
- **Nostr Reachability**: `NostrTransport.isPeerReachable(_:)` validates both mesh visibility and relay connectivity, ensuring Internet fallback only occurs when infrastructure is actually available.

### Prompt Delivery Requirements

The default implementation of `canDeliverPromptly(to:)` in [`bitchat/Services/Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Transport.swift) (lines 22-25) simply forwards the reachability test. This means a mesh-reachable peer is automatically considered "prompt." Nostr overrides this behavior to additionally require a live relay connection, preventing attempts to send via disconnected relays.

## Practical Message Routing Example

When sending a private message, the routing logic remains transparent to the caller but executes the priority selection behind the scenes:

```swift
// Sending a private message – routing logic hidden inside MessageRouter
router.sendPrivate("Hey nearby!", to: peerID, recipientNickname: "Alice", messageID: "msg-001")
/* 
   - reachableTransport(for:) scans `transports` in order.
   - If `meshService.isPeerReachable(peerID)` → BLEService is used.
   - Otherwise, if `nostrTransport.isPeerReachable(peerID)` (relay connected) → Nostr.
   - If both fail, the message is queued for later retry.
*/

```

This implementation ensures that if the target peer is **Bluetooth-mesh reachable**, the router selects **BLEService** and transmits directly over the local mesh. If the peer is **not mesh-reachable** but the app has an active Nostr relay connection, the router falls back to **NostrTransport**. When **neither** transport can reach the peer, `MessageRouter` queues the message for later retry using its internal outbox logic.

## Summary

- **BitChat's dual transport architecture** prioritizes local Bluetooth mesh connections over Internet-based Nostr relays to minimize latency and maximize privacy.
- **Transport selection** relies on an ordered array `[meshService, nostrTransport]` where the first reachable transport wins.
- **Reachability detection** differs by transport: BLE checks for radio visibility while Nostr requires both visibility and active relay WebSocket connections.
- **MessageRouter** handles automatic fallback and queuing, ensuring messages deliver via the fastest available path or retry later when offline.
- **Key files** driving this behavior include [`ChatViewModelBootstrapper.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatViewModelBootstrapper.swift) (configuration), [`MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessageRouter.swift) (routing logic), and the respective transport implementations in [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) and [`NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrTransport.swift).

## Frequently Asked Questions

### How does BitChat decide whether to use Bluetooth mesh or Nostr?

BitChat evaluates transports in the order defined in [`ChatViewModelBootstrapper.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatViewModelBootstrapper.swift): Bluetooth mesh first, then Nostr. The `MessageRouter` calls `reachableTransport(for:)` which returns the first transport where `isPeerReachable(_:)` returns `true`. This means Bluetooth always wins when both are technically available, ensuring local delivery preferred over Internet relay.

### What happens if a peer is reachable on both transports?

When a peer is visible on both Bluetooth mesh and Nostr relays, BitChat selects Bluetooth mesh because it appears first in the transports array. This design minimizes Internet dependency and reduces latency by using direct radio links rather than routing through remote relays.

### How does BitChat handle messages when no transport can reach the recipient?

When `reachableTransport(for:)` returns `nil`—meaning neither BLE nor Nostr reports reachability—the `MessageRouter` queues the message in an outbox for later retry. The system attempts redelivery when the recipient becomes reachable via either transport, ensuring messages survive temporary connectivity gaps.

### Why does NostrTransport require mesh reachability to report itself as reachable?

As implemented in [`NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrTransport.swift) lines 216-231, the `isPeerReachable(_:)` method checks both mesh visibility and relay connectivity. This dependency ensures that Nostr acts strictly as a bridge extension of the mesh rather than an independent discovery mechanism, maintaining the architecture's design principle that local mesh visibility governs all routing decisions.