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

> Discover how BitChat's dual transport architecture intelligently selects Bluetooth mesh for local messages and Nostr relays for broader reach. Learn about its smart communication fallback.

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

---

**BitChat's MessageRouter prioritizes Bluetooth mesh for direct local messaging and automatically falls back to Nostr relays only when the mesh cannot reach the target peer.**

BitChat implements a sophisticated dual transport architecture that seamlessly bridges local peer-to-peer networks with global internet relays. Developed by permissionlesstech/bitchat, this Swift-based messaging application intelligently routes messages through either Bluetooth Low Energy mesh networks or the Nostr protocol based on real-time reachability. The routing decision happens transparently in the background, ensuring messages always take the fastest available path.

## The Dual Transport Priority System

At the heart of BitChat's routing logic lies a strict priority ordering defined during app initialization. The system treats **Bluetooth mesh** as the primary transport and **Nostr** as the secondary fallback.

### Bootstrap Transport Ordering

In [`bitchat/ViewModels/ChatViewModelBootstrapper.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/ChatViewModelBootstrapper.swift) (lines 31-34), the bootstrapper explicitly creates the transport array with mesh 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 ordering is critical because `MessageRouter` always selects the first transport that reports positive reachability.

### First-Match Reachability Logic

The `MessageRouter.reachableTransport(for:)` method in [`bitchat/Services/MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/MessageRouter.swift) (lines 82-84) implements a simple but effective selection algorithm:

- It iterates through the `[meshService, nostrTransport]` array in order
- Returns the first transport where `isPeerReachable(_:)` returns `true`
- If no transport reports reachability, the message enters the outbox queue for later retry

## Transport Reachability Implementation

Each transport layer implements reachability differently based on its underlying network characteristics.

### Bluetooth Mesh Reachability

The `BLEService` class in [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift) (line 1116) determines mesh reachability by monitoring the Bluetooth stack for active links or recent advertisements. When a peer appears within the mesh reachability window, `isPeerReachable(_:)` returns `true`, causing the router to select this low-latency local path.

### Nostr Relay Reachability

In contrast, `NostrTransport` in [`bitchat/Services/NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NostrTransport.swift) (lines 216-231) implements stricter requirements. The `isPeerReachable(_:)` method returns `true` only when:

- The peer is reachable via the mesh (fallback check), **and**
- At least one Nostr relay WebSocket connection is active

Additionally, `canDeliverPromptly(to:)` in the base `Transport` protocol ([`bitchat/Services/Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Transport.swift), lines 22-25) defaults to forwarding the reachability test, but `NostrTransport` overrides this to require the live relay connection.

## Practical Message Routing Example

When sending a private message, developers interact with the high-level API while the dual transport architecture handles the complexity:

```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 Alice is physically nearby and Bluetooth is active, the message travels directly device-to-device without touching the internet. Only when Alice moves out of Bluetooth range does the architecture transparently switch to Nostr relays.

## Summary

BitChat's dual transport architecture provides resilient messaging through intelligent automatic fallback:

- **Priority ordering**: The bootstrapper configures `[meshService, nostrTransport]` to prefer local mesh networks
- **Reachability-based selection**: `MessageRouter.reachableTransport(for:)` selects the first transport reporting peer availability
- **BLE first**: Bluetooth mesh is chosen when `BLEService.isPeerReachable(_:)` detects active links or recent advertisements
- **Nostr fallback**: Internet-based Nostr relays activate only when the mesh cannot reach the peer and WebSocket connections are live
- **Automatic queuing**: Messages enter the outbox when neither transport reports reachability, ensuring eventual delivery

## Frequently Asked Questions

### What happens if both Bluetooth mesh and Nostr are available?

The architecture always prefers Bluetooth mesh when available. Because the `transports` array lists `meshService` first in [`ChatViewModelBootstrapper.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatViewModelBootstrapper.swift), and `MessageRouter` returns the first reachable transport, Nostr is only selected when the mesh explicitly reports the peer as unreachable.

### How does BitChat determine if a peer is reachable via Bluetooth?

According to [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift) (line 1116), the `isPeerReachable(_:)` method returns `true` when the Bluetooth stack observes an active link or a recent advertisement from the target peer within the defined mesh reachability window.

### Why does NostrTransport require a WebSocket connection for reachability?

As implemented in [`bitchat/Services/NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NostrTransport.swift) (lines 216-231), the `isPeerReachable(_:)` method validates that at least one Nostr relay maintains an active WebSocket connection. This prevents the router from selecting a transport that cannot immediately deliver the message, ensuring the fallback mechanism only activates when internet delivery is actually possible.

### Can the transport priority order be changed?

Yes. Developers can modify the `transports` array initialization in [`bitchat/ViewModels/ChatViewModelBootstrapper.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/ChatViewModelBootstrapper.swift) (lines 31-34). However, reversing the order would cause all messages to route through Nostr relays even when Bluetooth mesh is available, eliminating the latency and privacy benefits of local peer-to-peer messaging.