- Swift 49.8%
- Kotlin 22.1%
- Rust 16%
- JavaScript 5.4%
- Shell 1.6%
- Other 4.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
* fix: wire desktop chat delete/mute via right-click row actions Desktop sidebar rows were click-only, so the delete flow (pendingRowActions -> ChatRowActionsSheet -> DeleteChatSheet) was unreachable on desktop. Make the shared sheets internal and reuse them in SonarDesktopRoot, triggered by right-click on a sidebar row. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: move desktop right-click behind expect/actual for Android compile Modifier.onClick/PointerMatcher/PointerButton are desktop-only in foundation 1.7.3; commonMain is compiled by the Android target too. jvmMain gets the real right-click, androidMain a no-op (row actions there come from HomeScreen long-press). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add macOS sidebar mute/delete row actions for chat parity MacDMRow gets the same context menu the Compose desktop right-click and the iOS home long-press expose: mute (duration ladder) and delete/leave with a confirmation dialog, reusing SonarAppStore mute/delete methods. Closes the cross-platform gap flagged on the Compose-only change. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
| .github/workflows | ||
| apps/sonar | ||
| assets/notifications | ||
| benchmark | ||
| core | ||
| crashes | ||
| deploy | ||
| design | ||
| docs | ||
| ios | ||
| packages | ||
| relays | ||
| scripts | ||
| services/handle-registrar | ||
| web | ||
| .gitattributes | ||
| .gitignore | ||
| AGENTS.md | ||
| BRING_THE_NOISE.md | ||
| CLAUDE.md | ||
| Justfile | ||
| LICENSE | ||
| PRIVACY_POLICY.md | ||
| README.md | ||
| WHITEPAPER.md | ||
| zapstore.yaml | ||
Sonar
Sense who's nearby before you see them.
Sonar is a decentralized, privacy-first messenger and Lightning wallet. It speaks two transports — a local Bluetooth LE mesh for offline, proximity communication and the internet-based Nostr protocol for global reach — and adds end-to-end-encrypted group messaging (Marmot / MLS over Nostr), nearby Bluetooth payments, and peer-to-peer voice/video calls on top. No accounts, no phone numbers, no central servers — your identity is a key you hold.
Sonar grew out of bitchat and keeps its mesh + Nostr foundation, then rebuilds the product around a single shared Rust core that drives native iOS/macOS and Compose Multiplatform (Android/Desktop) apps.
License
This project is released into the public domain. See the LICENSE file for details.
Features
- Dual transport — Bluetooth LE mesh for offline, Nostr for internet-based messaging, with automatic transport selection (Bluetooth → Nostr fallback → smart queueing).
- White Noise secure DMs — end-to-end-encrypted group messaging via Marmot (MLS over Nostr); interoperable with the White Noise client in both directions.
- Encrypted media — image / file / voice sharing over the Marmot media module (MIP-04).
- ⚡ Lightning wallet — an embedded wallet (Breez SDK Liquid; BOLT12 / BIP-353 capable) tied to the same identity, so the same key restores the same wallet.
- Nearby payments over BLE — bidirectional, payments-only Lightning over Bluetooth (
⚡PAY), no chat required. - Voice & video calls — peer-to-peer
☎CALLover an iroh QUIC transport (NodeId-authenticated, NAT hole-punching). - Location channels — geographic chat rooms addressed by geohash, with live presence, over global Nostr relays.
- Tor by default — all internet traffic is routed through a local Tor SOCKS5 proxy, fail-closed when Tor isn't ready.
- Privacy first — no accounts, no phone numbers, no persistent identifiers; mesh DMs use the Noise Protocol (XX pattern), Nostr DMs use NIP-17 gift-wrapping.
- Multi-platform — native SwiftUI on iOS/macOS, Compose Multiplatform on Android/Desktop, all over one Rust core.
Architecture
Sonar is one headless Rust core, many thin app shells. The core owns identity, messaging, payments, and call signaling; each platform provides only the UI and the OS-specific bits (radios, storage, notifications, wallet).
core/ shared Rust core (Cargo workspace)
├── sonar-core/ identity + Marmot (MLS over Nostr) + geohash + mesh + call signaling
├── sonar-ffi/ UniFFI bindings → Swift (xcframework) + Kotlin (.so / JNA)
└── sonar-ble/ desktop BLE bridge (CoreBluetooth/BlueZ over JNA; own workspace)
ios/ native SwiftUI reference app (iOS + macOS)
apps/sonar/ Compose Multiplatform app (Android + Desktop JVM)
web/ SvelteKit marketing landing page
relays/ curated Nostr relay list
design/ vendored design handoff (source of truth for the UI)
docs/ protocol & feature specs (payments, discovery, calls, Tor, …)
The core is generated into each app by the build scripts in core/
(build-ios.sh, build-android.sh, build-desktop.sh) — they compile the Rust
staticlib/cdylib and emit the UniFFI Swift/Kotlin bindings. The shipped binary
artifacts (e.g. sonarffi.xcframework) are generated, not committed — rebuild
them before archiving.
Transports
Bluetooth mesh (offline) — direct peer-to-peer within range, multi-hop relay (up to 7 hops), no internet required, Noise-encrypted with forward secrecy, over a compact binary protocol tuned for BLE.
Nostr (internet) — global reach over a distributed relay network, NIP-17 gift-wrapped DMs, ephemeral per-geohash keys for location channels.
Channel types
| Channel | Transport | Scope |
|---|---|---|
mesh #bluetooth |
Bluetooth LE mesh | Local devices within multi-hop range — offline, protests, disasters, remote areas |
Location (block #dr5rsj7, neighborhood #dr5rs, country #dr) |
Nostr over internet | Geographic areas by geohash precision (block 7 → region 2 chars) |
Account model
Your identity is a Nostr keypair (npub/nsec). The same key restores the same
profile and the same Lightning wallet across devices and transports — a single
person is one conversation whether they're reachable over BLE or White Noise.
For protocol detail, see the Technical Whitepaper, the Noise
write-up in BRING_THE_NOISE.md, and the specs under
docs/ — including Hermes Agent integration for
headless sonar-cli + AI agents.
Platform support
| Capability | iOS / macOS | Android | Desktop (JVM) |
|---|---|---|---|
| White Noise (Marmot) secure DMs | ✅ | ✅ | ✅ |
| Geohash channels + presence | ✅ | ✅ | ✅ |
| Encrypted media (MIP-04) | ✅ | ✅ | ✅ |
| BLE mesh — discovery | ✅ | ✅ | ✅ (scan + advertise) |
| BLE mesh — messaging | ✅ | ✅ | ⚪️ next stage (Noise-over-GATT) |
Nearby payments over BLE (⚡PAY) |
✅ | ✅ | ⚪️ later |
| Lightning wallet | ✅ (Breez) | ✅ (Breez) | ⚪️ no desktop Breez build yet |
Voice / video calls (☎CALL) |
🚧 in progress | 🚧 in progress | 🚧 |
See apps/sonar/README.md for the full Android/Desktop matrix and its caveats.
Build & run
iOS / macOS
cd ios
open bitchat.xcodeproj
To run on a device:
-
Copy the local config:
cp Configs/Local.xcconfig.example Configs/Local.xcconfig -
Set your
DEVELOPMENT_TEAM(and, if you fork it,PRODUCT_BUNDLE_IDENTIFIER/APP_GROUP_ID) inConfigs/Local.xcconfig. The app group must staygroup.<bundle id>. -
The Rust core is a generated artifact — rebuild it before building the app:
./core/build-ios.sh
For a quick macOS run from source: brew install just && just run (and just clean afterwards to restore the project for mobile builds).
Android / Desktop (Compose Multiplatform)
cd apps/sonar
./gradlew :composeApp:installDebug # Android debug → device (arm64)
./gradlew :composeApp:assembleRelease # phone APK (arm64 + armeabi-v7a)
./gradlew :composeApp:assembleRelease -Psonar.universalApk=true # + emulators
./gradlew :composeApp:run # Compose Desktop
The Gradle build invokes the Rust core build (core/build-android.sh /
build-desktop.sh) to produce the JNI .so / host dylib and the Kotlin bindings.
Android details (secrets, ABIs, sideload): docs/ANDROID-BUILD.md.
Web (landing page)
cd web
npm install
npm run dev
Localization
- Base app resources live under
ios/bitchat/Localization/Base.lproj/. Add new copy toLocalizable.stringsand plural rules toLocalizable.stringsdict. - Share extension strings are separate in
ios/bitchatShareExtension/Localization/Base.lproj/Localizable.strings. - Prefer keys that describe intent (
app_info.features.offline.title) and reuse existing ones where possible. - Compile-check localization changes:
xcodebuild -project ios/bitchat.xcodeproj -scheme "bitchat (macOS)" -configuration Debug CODE_SIGNING_ALLOWED=NO build.