How BitChat Handles Message Routing Between Bluetooth and Nostr Transports

BitChat routes messages between Bluetooth Low-Energy (BLE) and Nostr transports using a capability-driven abstraction layer where the Transport protocol enables seamless switching based on peer reachability, allowing local mesh delivery via BLE and remote relay delivery via Nostr without changing the application API.

BitChat implements a sophisticated dual-transport architecture that automatically routes messages between Bluetooth Low-Energy mesh networks and Nostr relay protocols. This mechanism, implemented in the permissionlesstech/bitchat repository, relies on a unified Transport protocol abstraction that inspects peer capabilities to determine optimal message delivery paths. Understanding how BitChat handles message routing between Bluetooth and Nostr transports reveals how modern decentralized chat applications can bridge local mesh networking with global relay infrastructure.

The Transport Protocol Abstraction

At the core of BitChat's routing mechanism lies the Transport protocol defined in bitchat/Services/Transport.swift (lines 31-57). This protocol abstracts all network communication behind a single interface, presenting a unified API to the rest of the application regardless of whether messages travel over BLE mesh or Nostr relays.

The protocol defines essential methods for message delivery and peer inspection, including sendMessage(_:mentions:) and peerCapabilities(_:). By conforming to this protocol, both Bluetooth and Nostr implementations expose identical interfaces to higher-level components like ChatViewModel and ChatTransportEventCoordinator.

Two concrete transports implement this protocol:

  • BLEService – Handles Bluetooth Low-Energy mesh communication, conforming via extension BLEService: Transport at the end of Transport.swift (lines 107-113)
  • NostrTransport – Manages Nostr relay network communication via pub/sub mechanisms, defined in bitchat/Services/NostrTransport.swift

Peer Capabilities and Routing Decisions

BitChat determines the appropriate transport through capability-driven dispatch rather than hardcoded logic. Every peer advertises a PeerCapabilities value during discovery, defined in localPackages/BitFoundation/Sources/BitFoundation/PeerCapabilities.swift.

The routing decision follows this sequence:

  1. Capability Discovery – Peers advertise capabilities including the .nostrBridge flag, indicating the peer can bridge to the Nostr network
  2. Reachability Query – The view-model calls transport.peerCapabilities(peerID) to inspect the destination's supported transports
  3. Delivery Assessment – The system invokes transport.canDeliverPromptly(to:) to determine which transport can reach the peer

For Bluetooth-only peers, canDeliverPromptly(to:) evaluates to true when the BLE mesh reports active links. For Nostr bridge peers, the Nostr transport overrides this method to return true for Nostr-identified peers while BLE returns false, automatically routing the message through the appropriate medium.

Concrete Transport Implementations

BLEService: Local Mesh Transport

BLEService implements the Transport protocol to manage the Bluetooth Low-Energy mesh network. When sending to peers within direct BLE range, this transport handles message fragmentation, mesh routing, and delivery confirmation. It emits transport-specific events like .bluetoothStateUpdated that inform the UI of local connectivity changes.

NostrTransport: Global Relay Transport

NostrTransport conforms to the same Transport protocol but communicates with Nostr relays using the publish-subscribe pattern. When the routing logic identifies a bridge peer (one advertising .nostrBridge capabilities), messages queue through NostrTransport.sendMessage(_:mentions:), which serializes them into Nostr events and publishes them to configured relays for global delivery.

Unified Event Handling Across Transports

All transports emit a common TransportEvent enum containing cases like .messageReceived and .bluetoothStateUpdated. The ChatTransportEventCoordinator (located in bitchat/ViewModels/ChatTransportEventCoordinator.swift) subscribes to these events and forwards them to the appropriate UI or service layers.

This architecture ensures that higher-level code remains oblivious to the underlying medium. Whether a message arrives via BLE mesh from a nearby device or via Nostr relay from a remote peer, the coordinator processes it identically through the TransportEventDelegate protocol.

Implementation Examples

The following Swift code demonstrates the transport-agnostic routing logic:

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

// Route based on capabilities
if caps.contains(.nostrBridge) {
    // Automatically selects NostrTransport for bridge peers
    transport.sendMessage("Hello via Nostr", mentions: [])
} else {
    // Defaults to BLEService for local mesh peers
    transport.sendMessage("Hello via BLE", mentions: [])
}

Event handling remains consistent regardless of transport origin:

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

Summary

  • BitChat uses a Transport protocol abstraction defined in Transport.swift to unify BLE and Nostr implementations behind a single API
  • Capability-driven routing relies on PeerCapabilities (specifically .nostrBridge) to determine whether to use local BLE mesh or global Nostr relays
  • Reachability checking via canDeliverPromptly(to:) allows automatic transport selection without manual intervention
  • Unified event handling through ChatTransportEventCoordinator ensures transport-agnostic message processing for the UI layer

Frequently Asked Questions

How does BitChat decide between BLE and Nostr for message delivery?

BitChat queries peer capabilities using transport.peerCapabilities(peerID) to check for the .nostrBridge flag. It then evaluates canDeliverPromptly(to:) on available transports; BLE returns true for local mesh peers while Nostr returns true for bridge peers, automatically selecting the viable route.

What is the Transport protocol in BitChat?

The Transport protocol in bitchat/Services/Transport.swift defines the interface that both BLE and Nostr implementations must conform to, including methods for sending messages, checking peer capabilities, and assessing delivery reachability. This abstraction allows the Chat layer to remain transport-agnostic.

How does BitChat handle incoming messages from different transports?

All transports emit a unified TransportEvent enum consumed by ChatTransportEventCoordinator. Whether messages arrive via BLEService or NostrTransport, they generate identical .messageReceived events that the coordinator routes to the appropriate UI handlers without distinguishing the source medium.

What are PeerCapabilities in BitChat?

PeerCapabilities, defined in localPackages/BitFoundation/Sources/BitFoundation/PeerCapabilities.swift, is a flags-based descriptor that peers advertise during discovery. The .nostrBridge capability specifically indicates that a peer can route messages to the Nostr relay network, triggering the routing logic to select NostrTransport for delivery.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →