How to Contribute to BitChat: A Complete Guide for Open-Source Developers

You can contribute to BitChat by cloning the repository, using the just command runner for builds and tests, and following the architecture patterns in AppRuntime.swift and ConversationStore to add features or fix bugs.

BitChat is a decentralized peer-to-peer messaging app that combines a local Bluetooth mesh network with the global Nostr protocol. If you want to contribute to BitChat, you'll need to understand its split architecture—transport layer (Bluetooth + Nostr) and domain layer (state, routing, and UI models)—and follow the established build workflow using SwiftPM and the included Justfile.

Understanding BitChat Architecture Before You Contribute

Before writing code, study how BitChat separates concerns. The project is deliberately modular, making it easier to contribute to BitChat without breaking existing functionality.

Core Components Every Contributor Should Know

Component Role Key Source File
Dual Transport Offline Bluetooth mesh + online Nostr global chat README.md
AppRuntime Composition root that creates and owns all services bitchat/App/AppRuntime.swift (lines 13-30)
ConversationStore Single source of truth for messages and unread counts Referenced in AppRuntime.swift (lines 17-20)
Feature Models Fine-grained UI models that read/write the store PublicChatModel, PrivateInboxModel in AppRuntime.swift
Transport Protocol Abstract Transport protocol with BLEService implementation bitchat/Services/Transport.swift
VerificationService QR-code generation and identity bridging bitchat/Services/VerificationService.swift

The Architecture V2 refactor is ongoing. New contributions should place lifecycle, routing, and cross-cutting concerns in the runtime/store layer rather than the legacy ChatViewModel. See docs/ARCHITECTURE_V2.md for the full migration plan.

How Data Flows Through the System

When you contribute to BitChat, your code will likely touch one of these flow stages:

  1. App launch — BitchatApp instantiates AppRuntime as the composition root.
  2. Runtime initialization — Constructs ChatViewModel, ConversationStore, and feature models; binds observers for Nostr, Tor, and screenshot events.
  3. Transport selection — ChatViewModel attempts Bluetooth via BLEService first, falling back to Nostr through NostrIdentityBridge.
  4. State flow — All events route through AppEventStream → feature models → ConversationStore.
  5. UI updates — SwiftUI components read immutable snapshots from the store.

Setting Up Your Local Development Environment

To contribute to BitChat, start with a local build using the project's command runner.

Clone and Build


# Clone the repository

git clone https://github.com/permissionlesstech/bitchat.git
cd bitchat

# Install just (command runner)

brew install just

# Run the full CI check

just check

The just check command runs swift test, SwiftLint, and the performance-floor script. See Justfile for all available recipes.

Available Just Commands

Command Purpose
just test Run SwiftPM unit tests
just test-ios Run iOS simulator tests
just run Build and launch the app
just check Lint, format, and test (CI pipeline)

Contributing a New Feature: Step-by-Step Example

Here's how to contribute to BitChat by adding a "Pinned Channels" feature following project conventions.

Step 1: Create the Feature Model

Place UI-focused models in bitchat/App/ or bitchat/ViewModels/:

// bitchat/App/PinnedChannelsModel.swift
import Foundation
import Combine

final class PinnedChannelsModel: ObservableObject {
    @Published private(set) var pinned: [Geohash] = []

    func togglePin(_ channel: Geohash) {
        if let idx = pinned.firstIndex(of: channel) {
            pinned.remove(at: idx)
        } else {
            pinned.append(channel)
        }
    }
}

Step 2: Wire Into AppRuntime

Register your model in the composition root at bitchat/App/AppRuntime.swift:

// Inside AppRuntime.init(...) after existing model construction
self.pinnedChannelsModel = PinnedChannelsModel()

Step 3: Expose to SwiftUI

Inject via environmentObject in BitchatApp.swift so views can access it.

Step 4: Write Unit Tests

All contributions to BitChat require tests. Use the mock infrastructure:

// bitchatTests/PinnedChannelsModelTests.swift
import XCTest
@testable import bitchat

final class PinnedChannelsModelTests: XCTestCase {
    func testTogglePinAddsAndRemoves() {
        let model = PinnedChannelsModel()
        let gh = Geohash("dr5rsj7")!
        XCTAssertTrue(model.pinned.isEmpty)

        model.togglePin(gh)
        XCTAssertEqual(model.pinned, [gh])

        model.togglePin(gh)
        XCTAssertTrue(model.pinned.isEmpty)
    }
}

Step 5: Verify and Submit


# Run full test suite

just test

# Verify build integrity

swift run verify-build

Ensure your pull request:

Key Files to Reference When Contributing

Path Purpose
Package.swift SwiftPM dependencies and platform targets
docs/ARCHITECTURE_V2.md New runtime/store architecture roadmap
bitchat/App/AppRuntime.swift Service composition and lifecycle
bitchat/Services/Transport.swift Transport protocol and BLEService
bitchat/Services/VerificationService.swift Identity and verification logic
bitchatTests/ Full test suite with MockBLEService
docs/VERIFYING-A-BUILD.md Reproducible build verification

Testing Your Contributions Without Hardware

BitChat's test suite includes mock BLE services that let you contribute to BitChat's mesh networking features without physical Bluetooth hardware:

  • MockBLEService — Simulates Bluetooth transport for unit tests
  • MockBLEBus — Simulates peer discovery and message routing

These mocks enable fast, deterministic tests. Run them with just test before submitting any pull request.

Summary

  • Clone and build with just check to verify your environment
  • Understand the architecture: AppRuntime composes services, ConversationStore owns state, feature models handle UI logic
  • Follow the V2 roadmap: Place new code in the runtime/store layer, not legacy ChatViewModel
  • Write tests using MockBLEService and other mocks for hardware-free verification
  • Use just commands for consistent builds: test, check, run
  • Verify builds with swift run verify-build before submitting PRs

Frequently Asked Questions

What programming language does BitChat use?

BitChat is written in Swift using SwiftUI for the interface and SwiftPM for dependency management. The entire stack—including Bluetooth mesh networking, Nostr protocol integration, and encryption—is native Swift code, making it accessible to iOS developers who want to contribute to BitChat.

Do I need physical Bluetooth hardware to test my changes?

No. The bitchatTests/ directory includes MockBLEService and MockBLEBus classes that simulate Bluetooth transport, peer discovery, and message routing. You can write and run deterministic unit tests entirely in the simulator or on macOS without iOS hardware.

Where should I place new feature code?

New features belong in feature models (like PublicChatModel, PrivateInboxModel) that read and write to ConversationStore rather than the monolithic ChatViewModel. Wire your model into AppRuntime.swift as the composition root, following the Architecture V2 patterns documented in docs/ARCHITECTURE_V2.md.

How do I run the same checks as CI locally?

Execute just check from the repository root. This runs SwiftLint, formatting checks, and the full swift test suite. The command is defined in Justfile and matches the GitHub Actions workflow exactly.

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 →