How BitChat's Dual Transport Architecture Chooses Between Bluetooth Mesh and Nostr
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 (lines 31-34), the bootstrapper explicitly creates the transport array with mesh first:
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 (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(_:)returnstrue - 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 (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 (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, 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:
// 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, 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 (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 (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 (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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →