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

> Explore how BitChat's dual transport architecture uses Bluetooth mesh and Nostr. Discover reliable P2P messaging with automatic fallback for seamless communication.

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

---

**BitChat's dual transport architecture prioritizes Bluetooth mesh for local peer-to-peer messaging and automatically falls back to Nostr relays when the mesh cannot reach the recipient, with the MessageRouter selecting the first available transport based on real-time reachability tests.**

BitChat, an open-source messaging application from permissionlesstech/bitchat, implements a sophisticated dual transport architecture that combines local Bluetooth mesh networking with global Nostr relay infrastructure. This hybrid approach ensures messages reach their destination through the most efficient path available, prioritizing direct peer-to-peer connections over internet-based relays. Understanding how BitChat's dual transport architecture functions reveals a clever prioritization system that minimizes latency while maintaining connectivity across diverse network conditions.

## Transport Layer Components

BitChat ships with two independent transport implementations that serve distinct networking purposes:

- **Bluetooth Mesh (BLEService)**: A peer-to-peer radio mesh enabling direct device-to-device communication without internet connectivity. This transport reports reachability when the Bluetooth stack observes a peer link or recent advertisement.
- **Nostr (NostrTransport)**: An internet-backed relay network requiring an active WebSocket connection to at least one Nostr relay to be considered reachable.

The system evaluates these transports in sequence, always attempting the local mesh first before consuming internet bandwidth.

## How MessageRouter Selects the Transport

The routing decision logic resides in [`MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessageRouter.swift), which orchestrates transport selection through ordered reachability testing.

### Transport Priority Order

The bootstrapper establishes transport precedence during initialization. In [`ChatViewModelBootstrapper.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatViewModelBootstrapper.swift) (lines 31-34), the code creates an ordered array placing the mesh service first:

```swift
let transports: [Transport] = [meshService, nostrTransport]
let router = MessageRouter(transports: transports)

```

This array order determines selection priority. The router iterates through this list sequentially, making Bluetooth mesh the preferred path whenever available.

### Reachability Testing Implementation

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

```swift
func reachableTransport(for peerID: PeerID) -> Transport? {
    return transports.first { $0.isPeerReachable(peerID) }
}

```

This method returns the first transport reporting reachability for the target peer, enabling automatic fallback if the primary transport fails.

### Bluetooth Mesh Reachability

`BLEService.isPeerReachable(_:)` determines mesh availability by checking the Bluetooth stack state. According to [`BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEService.swift) (line 1116), the method returns `true` when the peer is actively observed or was seen within the mesh reachability window. This ensures recent proximity triggers mesh selection even if the link fluctuates momentarily.

### Nostr Relay Requirements

`NostrTransport.isPeerReachable(_:)` imposes stricter connectivity requirements. As implemented in [`NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrTransport.swift) (lines 216-231), this method returns `true` only when the mesh reports reachability **and** at least one Nostr relay maintains an active WebSocket connection. This dual requirement prevents attempting relay delivery when internet connectivity is absent.

Additionally, [`Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/Transport.swift) (lines 22-25) defines a default `canDeliverPromptly(to:)` implementation that forwards to `isPeerReachable`, ensuring mesh-reachable peers are marked as prompt delivery candidates. Nostr overrides this to require the live relay connection.

## Message Routing Flow and Fallback Behavior

When sending a message, the router executes a deterministic fallback sequence:

1. Check if target is mesh-reachable via `BLEService` (first in array)
2. If mesh fails, verify Nostr relay connectivity via `NostrTransport`
3. If neither transport reaches the peer, queue the message for later retry using the outbox logic in [`MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessageRouter.swift)

This architecture guarantees low-latency local delivery when possible, transparently bridging to the internet only when necessary.

Example implementation:

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

// Send with automatic transport selection
router.sendPrivate("Hey nearby!", to: peerID, recipientNickname: "Alice", messageID: "msg-001")
// Router selects BLEService if peer is nearby, otherwise falls back to Nostr

```

## Summary

BitChat's dual transport architecture delivers robust messaging through intelligent path selection:

- **Priority-based routing**: The `[meshService, nostrTransport]` array order ensures Bluetooth mesh always takes precedence
- **Real-time reachability**: `MessageRouter.reachableTransport(for:)` evaluates `isPeerReachable` on each transport sequentially
- **Mesh-first local delivery**: `BLEService` provides direct peer-to-peer connectivity without internet dependency
- **Nostr fallback**: Internet relay activates only when mesh reachability fails or the peer requires remote connectivity
- **Automatic queuing**: Messages queue for retry when neither transport reports reachability

## Frequently Asked Questions

### How does BitChat decide between Bluetooth and Nostr for message delivery?

BitChat evaluates transports in a fixed priority order defined in [`ChatViewModelBootstrapper.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatViewModelBootstrapper.swift). The `MessageRouter` checks `isPeerReachable` for each transport sequentially, selecting the first that returns true. Bluetooth mesh appears first in the array, so the system attempts local delivery before checking Nostr relay connectivity.

### What happens if a peer is reachable on both Bluetooth mesh and Nostr?

The Bluetooth mesh transport always wins when both are available because `MessageRouter.reachableTransport(for:)` uses `transports.first` to select the initial match. Since [`ChatViewModelBootstrapper.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatViewModelBootstrapper.swift) initializes the array with mesh first, messages route through the local radio rather than consuming internet bandwidth.

### Can BitChat send messages when neither transport is reachable?

No, but the system implements outbox queuing in [`MessageRouter.swift`](https://github.com/permissionlesstech/bitchat/blob/main/MessageRouter.swift). If `reachableTransport(for:)` returns nil for both `BLEService` and `NostrTransport`, the message enters a retry queue. The router attempts redelivery automatically when either transport subsequently reports reachability for the target peer.

### Does Nostr require an active internet connection to be considered reachable?

Yes. According to [`NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrTransport.swift) lines 216-231, `isPeerReachable` returns true only when the app maintains an active WebSocket connection to at least one Nostr relay. Without this connection, the transport reports unreachable, forcing the router to queue the message or wait for mesh connectivity.