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

> Contribute to the BitChat open-source project. Clone the repo, use just commands, and follow architecture patterns in AppRuntime Swift to add features or fix bugs. Get started today.

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

---

**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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/README.md) |
| **AppRuntime** | Composition root that creates and owns all services | [`bitchat/App/AppRuntime.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/App/AppRuntime.swift) (lines 13-30) |
| **ConversationStore** | Single source of truth for messages and unread counts | Referenced in [`AppRuntime.swift`](https://github.com/permissionlesstech/bitchat/blob/main/AppRuntime.swift) (lines 17-20) |
| **Feature Models** | Fine-grained UI models that read/write the store | `PublicChatModel`, `PrivateInboxModel` in [`AppRuntime.swift`](https://github.com/permissionlesstech/bitchat/blob/main/AppRuntime.swift) |
| **Transport Protocol** | Abstract `Transport` protocol with `BLEService` implementation | [`bitchat/Services/Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Transport.swift) |
| **VerificationService** | QR-code generation and identity bridging | [`bitchat/Services/VerificationService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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

```bash

# 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/`:

```swift
// 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`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/App/AppRuntime.swift):

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

```

### Step 3: Expose to SwiftUI

Inject via `environmentObject` in [`BitchatApp.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BitchatApp.swift) so views can access it.

### Step 4: Write Unit Tests

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

```swift
// 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

```bash

# Run full test suite

just test

# Verify build integrity

swift run verify-build

```

Ensure your pull request:
- Passes `swiftlint` (config in [`.swiftlint.yml`](https://github.com/permissionlesstech/bitchat/blob/main/.swiftlint.yml))
- Passes CI workflow ([`.github/workflows/swift-tests.yml`](https://github.com/permissionlesstech/bitchat/blob/main/.github/workflows/swift-tests.yml))
- References related issue numbers

## Key Files to Reference When Contributing

| Path | Purpose |
|------|---------|
| [`Package.swift`](https://github.com/permissionlesstech/bitchat/blob/main/Package.swift) | SwiftPM dependencies and platform targets |
| [`docs/ARCHITECTURE_V2.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/ARCHITECTURE_V2.md) | New runtime/store architecture roadmap |
| [`bitchat/App/AppRuntime.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/App/AppRuntime.swift) | Service composition and lifecycle |
| [`bitchat/Services/Transport.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Transport.swift) | `Transport` protocol and `BLEService` |
| [`bitchat/Services/VerificationService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/VerificationService.swift) | Identity and verification logic |
| `bitchatTests/` | Full test suite with `MockBLEService` |
| [`docs/VERIFYING-A-BUILD.md`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/AppRuntime.swift) as the composition root, following the Architecture V2 patterns documented in [`docs/ARCHITECTURE_V2.md`](https://github.com/permissionlesstech/bitchat/blob/main/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.