Add an embedded Tor daemon (kmp-tor) with an onion toggle in the sidebar
drawer and on the pre-login Splash/Auth screens. When enabled, every
connection — relay WebSockets, Coil images, ExoPlayer media, NIP-05/NIP-11,
LNURL/zaps, Blossom uploads, link previews, exchange rates, DM media, live
metrics — routes through Tor's SOCKS proxy.
Routing is fail-closed: from the moment the toggle flips, new clients point
at a dead loopback port until bootstrap completes, and existing clients have
their in-flight requests cancelled and keep-alive pools evicted so no
pre-toggle socket survives. OkHttp passes unresolved hostnames to SOCKS
proxies, so DNS also resolves inside Tor.
- TorManager: kmp-tor runtime in a foreground service; state machine
(Off/Starting %/On/Stopping/Error) observed by all toggles; start retries
around kmp-tor's fixed 1s service-bind timeout, which a busy main thread
misses during cold start
- TorPreferences: device-level pref so the toggle works pre-login and
survives logout
- HttpClientFactory: single proxy choke point; client registry +
setTorSocks() invalidation; new getRelayClient/getLoopbackClient/
getDmMediaClient
- Relay/RelayPool: clients resolved through providers on every (re)connect;
suspendForTorSwitch/resumeAfterTorSwitch tear down sockets and suppress
reconnects during bootstrap so the 5-min relay cooldown can't trip; local
relay stays direct (Tor refuses loopback/LAN targets)
- Captured-client fixes so a mid-session toggle can't leak: Coil delegating
Call.Factory (it caches its lambda forever), ZapSender, DmBubble,
NwcRepository, SplashViewModel raw client, ExchangeRateRepository refetch
- Cold start with Tor on: relay init gated on bootstrap
- kmp-tor pinned to 2.4.x: 2.5+ needs Kotlin 2.2 metadata, unreadable by
this project's Kotlin 2.0.21
Verified on-device by socket-inode audit: app process held 43 sockets, all
to 127.0.0.1:<socks>; only the tor child process held external connections.
Known gaps: Breez/Spark SDK (own Rust networking) and ML Kit model
downloads bypass OkHttp and are not routed.
Fully revert f68b40f, which shipped in 1.0.2 alongside reports of severe
slowdown and freezes on GrapheneOS:
- jniLibs.useLegacyPackaging back to true: compressed native libs cut the
download from 82MB to 37MB, and uncompressed 16KB-aligned packaging is
only needed for Google Play, which this build no longer targets
- secp256k1-kmp pinned back to known-good 0.16.0 (0.19.0 = libsecp256k1
0.7.0 rebuilt with NDK 28; runs once per relay event on the verify path)
Also fix adjacent issues found while auditing every shipped release APK
(all were clean: release-signed, R8-minified, correctly aligned — ruling
out a malformed artifact as the cause):
- baseline-prof.txt still targeted com/wisp/app after the rebrand; the
next release would have shipped zero app AOT rules. Fixed paths now
yield 12,888 app rules; verified on-device (cold start 2048ms -> 830ms
once compiled, ProfileVerifier reports compiledWithProfile=true)
- release signing moved into Gradle (RELEASE_STORE_* via local.properties
or env) so the published APK is exactly what assembleRelease emits
- tools/audit-apk.sh: pre-release gate checking debuggable flag, lib
packaging consistency, alignment, R8, baseline profile, signing cert
- ProfileVerifier status logged at startup for field diagnostics
- <profileable android:shell="true"/> so testers can capture simpleperf/
Perfetto traces from release builds
- docs/grapheneos-perf-investigation.md: full findings plus the A/B
attribution matrix (incl. GrapheneOS MTE toggle test) for confirming
which half of f68b40f was guilty
Drops the embedded kmp-tor client, all .onion relay support, the Tor
toggle UI on splash/auth/drawer screens, the SOCKS proxy plumbing in
HttpClientFactory, all Tor-related strings across 11 locales, the
TorManager singleton, and the kmp-tor dependencies and ProGuard rules.
Relay URL validation now accepts only wss:// (with hostname, no port,
non-localhost, non-IP). The cleartextTrafficPermitted network security
flag stays — still needed for local relays on RFC1918 / loopback.
Disable legacy jniLibs packaging so native libraries are stored
uncompressed and page-aligned in the APK. Bump secp256k1-kmp to 0.19.0
and kmp-tor-resource to 408.16.4 since the prior versions shipped
ELF binaries with 4 KB LOAD alignment. All 13 .so files in the release
APK now report 2**14 alignment.
Prevent the device from sleeping during fullscreen video playback with
FLAG_KEEP_SCREEN_ON. Add a MediaSessionService so Android shows media
controls in the notification shade and lock screen when the app is
backgrounded, covering both fullscreen and PiP video modes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add CameraX + ML Kit barcode scanning to send flow (scan QR / import from gallery)
- Show fee for outgoing transactions in history
- Associate zap counterparties (sender/recipient) with transactions using:
- Persisted ZapSender map (SharedPreferences, survives restarts)
- ObjectBox zap receipt queries (kind 9735)
- One-shot relay fetch for user's incoming zap receipts (#p filter)
- Show counterparty avatar and display name in transaction rows
- Enable Spark SDK LNURL verify support (NIP-57 zap receipts)
- Sync wallet before listing transactions for freshest data
- Cache zap receipt counterparty maps to avoid repeated bolt11 parsing
- Move heavy transaction processing to Dispatchers.IO
Integrate Breez SDK Spark as a second wallet backend alongside NWC.
Users can create/restore a non-custodial Lightning wallet directly
in the app with seamless onboarding (no forced backup on first use).
- WalletProvider interface abstracts NWC and Spark backends
- WalletModeRepository persists active wallet mode
- SparkRepository wraps Breez SDK: connect, send, receive, transactions
- Wallet mode selection UI with back navigation
- Receive flow auto-detects payment via sync polling + SDK events
- Payment received success screen with auto-navigation
- Dynamic wallet provider resolution for zaps (no stale captures)
- Proper SDK lifecycle: disconnect on app exit, cleanup on teardown
- 60s connection timeout for Spark (vs 20s for NWC)
- Wallet-connected indicator works for both providers
- BIP39 mnemonic generation with bundled English wordlist
- ProGuard rules for UniFFI/JNA bindings
ExoPlayer was using its default HTTP stack, bypassing the SOCKS proxy
entirely and leaking the user's IP to video servers even with Tor active.
Added media3-datasource-okhttp to pipe video traffic through the same
Tor-aware OkHttpClient used by image loading and relay connections.
Add ObjectBox as a write-behind persistence layer for Nostr events.
Events flowing through the relay pipeline are asynchronously batched
and written to ObjectBox on Dispatchers.IO. The LRU cache remains the
hot read path — no startup seeding or feed changes in this PR.
What's persisted: user's own events (all kinds), notes (kind 1),
profiles (kind 0), reposts (kind 6), reactions (kind 7), and zap
receipts (kind 9735).
Search now merges LRU cache results with ObjectBox for cross-session
note search. Old events are pruned on startup (50k cap, 90-day TTL,
user's own events exempt).
Add a Media tab to user profile screens showing an Instagram-style grid
of images and videos extracted from the user's notes. Videos display
actual frame thumbnails via Coil's VideoFrameDecoder with a play icon
overlay. The grid supports infinite scroll.
Also adds ICQ-style flower burst animation for reply notifications,
restores the shared OkHttpClient for image loading (fixes GC thrashing
from per-request client creation), and separates reply vs generic
notification sounds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add profileinstaller library so baseline profiles are applied on
sideloaded APKs (not just Play Store installs). Add app-specific
baseline-prof.txt covering startup-critical paths. Enable resource
shrinking for release builds.
Use Google ML Kit for on-device translation with no API keys required.
Adds a "Translate" option to the post overflow menu that detects the
source language, downloads the model if needed, and displays the
translated text inline below the original content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Route all network traffic through Tor when enabled — relay WebSockets,
NIP-05, NIP-11, image loading, media uploads, and zaps. Uses kmp-tor
to embed the Tor runtime with automatic SOCKS5 port discovery.
- Add TorManager singleton for Tor lifecycle management
- Add HttpClientFactory to centralize OkHttpClient creation with
Tor proxy and DNS leak prevention (custom Dns resolver forces
hostname resolution through SOCKS5 tunnel)
- Migrate all OkHttpClient creation sites to use HttpClientFactory
- Add Tor-aware Coil image loading via Call.Factory
- Support .onion relay addresses when Tor is active
- Add onion icon toggle in navigation drawer with status indication
- Persist Tor enabled state across app restarts
- RelayPool hot-swaps clients on Tor state changes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>