Sonar - you sense who's nearby before you see them; makes Bluetooth peer-discovery feel like a feature, not plumbing. http://sonarprivacy.xyz/
  • Swift 49.8%
  • Kotlin 22.1%
  • Rust 16%
  • JavaScript 5.4%
  • Shell 1.6%
  • Other 4.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Vincenzo Palazzo 733843db36
Wire desktop chat delete/mute via right-click row actions (#402)
* 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>
2026-07-22 20:00:29 +02:00
.github/workflows Extract generic UIKit transcript host and buildable samples (#355) 2026-07-19 14:27:33 +02:00
apps/sonar Wire desktop chat delete/mute via right-click row actions (#402) 2026-07-22 20:00:29 +02:00
assets/notifications MSN-style trill (nudge) + per-chat mute (#336) 2026-07-20 00:28:58 +02:00
benchmark perf(ios): cold-start + Marmot relay-sync benchmark harness (#130) 2026-06-25 19:18:35 +02:00
core Fix push-open empty chat: catch-up + immediate refresh (#330) 2026-07-22 04:00:54 +02:00
crashes fix(ios): keep Marmot SQLite readable in background to stop SIGBUS crash (#133) 2026-06-25 19:29:43 +02:00
deploy fix(android): recognize content_available transponder pushes so killed-app notifications fire (#261) 2026-07-14 23:40:04 +02:00
design Start chat by username (drop npub-only Start a chat row) (#333) 2026-07-19 20:33:41 +02:00
docs Fix push-open empty chat: catch-up + immediate refresh (#330) 2026-07-22 04:00:54 +02:00
ios Wire desktop chat delete/mute via right-click row actions (#402) 2026-07-22 20:00:29 +02:00
packages Extract generic UIKit transcript host and buildable samples (#355) 2026-07-19 14:27:33 +02:00
relays Automated update of relay data - Sun Jun 21 07:51:14 UTC 2026 2026-06-21 07:51:15 +00:00
scripts docs(perf): publish 2026-07-20 iPhone cold-start results (#388) 2026-07-20 06:03:32 +02:00
services/handle-registrar Unified handles: claim vincenzo@sonarprivacy.xyz for chat + payments (#282) 2026-07-18 16:46:37 +02:00
web Blog: nicknames, handles, and readable group links (#389) 2026-07-20 06:47:53 +02:00
.gitattributes Prevent Github Languages stats skewing (#630) 2025-09-16 06:42:36 -07:00
.gitignore perf(android): Baseline Profiles + chat-open frame tracking (#305) (#306) 2026-07-17 09:30:41 +02:00
AGENTS.md Make Sonar transcripts local-first (#60) 2026-06-18 22:58:27 +02:00
BRING_THE_NOISE.md Perf: Add final to classes that are not inherited (#574) 2025-09-11 19:17:04 +02:00
CLAUDE.md fix: dismiss keyboard when opening emoji/sticker tray (#352) 2026-07-18 22:01:47 +02:00
Justfile build: brand dev builds as Sonar (name, icon, app id) 2026-06-17 22:28:13 +02:00
LICENSE Add public domain license and copyright headers 2025-07-04 16:21:49 +02:00
PRIVACY_POLICY.md Update PRIVACY_POLICY.md 2025-07-10 21:00:12 +05:30
README.md feat: Zapstore Android listing + website downloads (#194) 2026-07-10 13:58:12 +02:00
WHITEPAPER.md docs: extend whitepaper into the Sonar protocol spec 2026-06-17 00:51:27 +02:00
zapstore.yaml chore(release): bump to 0.1-alpha.11 (iOS 1.11.0/27) 2026-07-18 02:06:00 +02:00

Sonar

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 ☎CALL over 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) in Configs/Local.xcconfig. The app group must stay group.<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 to Localizable.strings and plural rules to Localizable.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.