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:
- App launch —
BitchatAppinstantiatesAppRuntimeas the composition root. - Runtime initialization — Constructs
ChatViewModel,ConversationStore, and feature models; binds observers for Nostr, Tor, and screenshot events. - Transport selection —
ChatViewModelattempts Bluetooth viaBLEServicefirst, falling back to Nostr throughNostrIdentityBridge. - State flow — All events route through
AppEventStream→ feature models →ConversationStore. - 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:
- Passes
swiftlint(config in.swiftlint.yml) - Passes CI workflow (
.github/workflows/swift-tests.yml) - References related issue numbers
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 testsMockBLEBus— 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 checkto verify your environment - Understand the architecture:
AppRuntimecomposes services,ConversationStoreowns state, feature models handle UI logic - Follow the V2 roadmap: Place new code in the runtime/store layer, not legacy
ChatViewModel - Write tests using
MockBLEServiceand other mocks for hardware-free verification - Use
justcommands for consistent builds:test,check,run - Verify builds with
swift run verify-buildbefore 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →