# How BitChat Routes Messages Between Bluetooth and Nostr Transports

> Discover how BitChat routes messages between Bluetooth and Nostr using a unified Transport protocol and peer capability flags for efficient local BLE mesh or remote Nostr relay communication.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: how-to-guide
- Published: 2026-08-19

---

**BitChat routes messages by abstracting Bluetooth Low-Energy (BLE) and Nostr networks behind a unified `Transport` protocol, using peer capability flags to determine whether to send via local BLE mesh or remote Nostr relays.**

The `permissionlesstech/bitchat` repository implements a hybrid messaging system that seamlessly switches between local Bluetooth mesh networks and decentralized Nostr relays. This article examines the capability-driven routing mechanism that enables intelligent transport selection without exposing complexity to the user interface layer.

## The Transport Abstraction Layer

BitChat centralizes all network communication behind a single **`Transport`** protocol defined in [`bitchat/Services/Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Transport.swift)【1†L31-L57】. This protocol exposes a common interface for sending messages, querying peer capabilities, and checking delivery feasibility.

Two concrete implementations conform to this protocol:

- **`BLEService`** – Manages the Bluetooth Low-Energy mesh network through an extension declared at the end of [`Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/Transport.swift)【1†L107-L113】
- **`NostrTransport`** – Handles communication with Nostr relays via a separate extension in [`NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrTransport.swift)

Both implementations override the default protocol methods to report their specific delivery capabilities and reachability status.

## Capability-Driven Routing Logic

The routing decision depends on **peer capabilities** advertised during discovery. In [`localPackages/BitFoundation/Sources/BitFoundation/PeerCapabilities.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/PeerCapabilities.swift), peers advertise a `PeerCapabilities` value that includes flags such as `.nostrBridge`.

When the **Chat layer** (specifically `ChatViewModel` or `ChatTransportEventCoordinator`) prepares to send a message, it executes the following capability lookup:

1. **Query capabilities** – Call `transport.peerCapabilities(peerID)` to retrieve the peer's advertised features. The default implementation in [`Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/Transport.swift) returns an empty set, while concrete transports override this to return actual detected capabilities.

2. **Evaluate delivery feasibility** – Invoke `transport.canDeliverPromptly(to:)` to determine reachability. The BLE transport returns `true` for peers within the local mesh, while the Nostr transport returns `true` only for peers identified by Nostr public keys.

3. **Dispatch to appropriate transport** – Based on the capability check, the message routes through either `BLEService.sendMessage(...)` for local mesh delivery or `NostrTransport.sendMessage(...)` for remote relay transmission.

## Message Flow Implementation

The following Swift patterns demonstrate how BitChat implements transport-agnostic routing while maintaining transport-specific optimizations.

### Checking Peer Capabilities

Before sending, the view-model inspects the destination peer's supported protocols:

```swift
// Query the transport for peer capabilities
let caps = transport.peerCapabilities(peerID)

// Route based on capability flags
if caps.contains(.nostrBridge) {
    // Target is a Nostr bridge peer; NostrTransport handles delivery
    transport.sendMessage("Hello via Nostr", mentions: [])
} else {
    // Target available via local BLE mesh
    transport.sendMessage("Hello via BLE", mentions: [])
}

```

### Unified Event Handling

All transports emit events through a common **`TransportEvent`** enum (defined in [`Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/Transport.swift)), which includes cases such as `.messageReceived` and `.bluetoothStateUpdated`. The `ChatTransportEventCoordinator` (located in [`bitchat/ViewModels/ChatTransportEventCoordinator.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/ChatTransportEventCoordinator.swift)) subscribes to these events and routes them to the UI layer without revealing the underlying medium:

```swift
extension ChatViewModel: TransportEventDelegate {
    func didReceiveTransportEvent(_ event: TransportEvent) {
        switch event {
        case .messageReceived(let msg):
            // Handles messages from either BLE or Nostr uniformly
            handleIncomingMessage(msg)
        case .bluetoothStateUpdated(let state):
            // Only BLEService emits this; NostrTransport never does
            updateBluetoothUI(state)
        default: 
            break
        }
    }
}

```

This architecture ensures that Bluetooth-specific state updates (like radio power changes) only propagate when relevant, while message reception remains entirely transport-agnostic.

## Key Files and Architecture

The routing mechanism spans several critical files in the `permissionlesstech/bitchat` repository:

| File | Role |
|------|------|
| [`bitchat/Services/Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Transport.swift) | Defines the `Transport` protocol, default implementations, and reachability helpers |
| [`bitchat/Services/NostrTransport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/NostrTransport.swift) | Concrete Nostr implementation that overrides capability checks for bridge peers |
| [`bitchat/Services/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLEService.swift) | BLE mesh implementation conforming to `Transport` via protocol extension |
| [`bitchat/ViewModels/ChatTransportEventCoordinator.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/ChatTransportEventCoordinator.swift) | Central event router that listens for `TransportEvent` emissions |
| [`localPackages/BitFoundation/Sources/BitFoundation/PeerCapabilities.swift`](https://github.com/permissionlesstech/bitchat/blob/main/localPackages/BitFoundation/Sources/BitFoundation/PeerCapabilities.swift) | Defines capability flags including `.nostrBridge` that drive routing decisions |

## Summary

- **BitChat uses a protocol-oriented abstraction** where both BLE and Nostr networks implement the same `Transport` interface.
- **Routing decisions rely on capability flags** queried via `peerCapabilities()`, specifically checking for `.nostrBridge` to identify Nostr-capable peers.
- **Delivery feasibility** is determined by `canDeliverPromptly(to:)`, allowing the system to prefer local Bluetooth mesh when available and fall back to Nostr relays for remote peers.
- **Event handling is unified** through the `TransportEvent` enum and `ChatTransportEventCoordinator`, keeping transport details encapsulated from the UI layer.

## Frequently Asked Questions

### How does BitChat determine if a peer supports Nostr?

BitChat examines the `PeerCapabilities` structure advertised during peer discovery. If the capabilities set contains `.nostrBridge` (defined in [`PeerCapabilities.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PeerCapabilities.swift)), the peer acts as a bridge between the local BLE mesh and the Nostr relay network. The `transport.peerCapabilities(peerID)` method retrieves these flags, with each concrete transport overriding the default implementation to return accurate capability data.

### What happens if a peer has both Bluetooth and Nostr capabilities?

The routing logic prioritizes based on delivery feasibility rather than capability presence alone. The system calls `canDeliverPromptly(to:)` on the available transports. If the peer is reachable via BLE (within radio range), the BLE transport returns `true` and handles delivery. If the peer is only reachable via Nostr (out of Bluetooth range), the Nostr transport returns `true` for that specific peer identifier, and the message routes through the relay network.

### How does the Transport protocol unify BLE and Nostr events?

Both transport implementations emit events through the `TransportEvent` enum, which includes generic cases like `.messageReceived` alongside transport-specific cases like `.bluetoothStateUpdated`. The `ChatTransportEventCoordinator` subscribes to these events from all active transports and dispatches them to view-models. This allows the UI to handle incoming messages identically regardless of origin, while still responding to Bluetooth-specific state changes when necessary.

### Where is the routing decision made in the codebase?

The decision logic resides in the chat layer, specifically within `ChatViewModel` or `ChatTransportEventCoordinator`. These components query capabilities via the `Transport` protocol interface, evaluate reachability using `canDeliverPromptly(to:)`, and then invoke `sendMessage` on the appropriate concrete implementation. The transports themselves expose uniform APIs but implement transport-specific delivery logic internally.