Implements P2P calling over Nostr relays using WebRTC for media
transport and NIP-59 Gift Wraps for encrypted signaling. No custom
server required — only public STUN servers for NAT traversal.
Protocol layer (quartz/nip100WebRtcCalls):
- 6 new event kinds (25050-25055): offer, answer, ICE candidate,
hangup, reject, renegotiate
- WebRtcCallFactory for creating and gift-wrapping signaling events
- CallIdTag and CallTypeTag for event metadata
- Events registered in EventFactory
Call state machine (commons/call):
- CallState sealed interface with full lifecycle states
- CallManager orchestrating signaling and state transitions
- Follow-gate spam prevention: only followed users can ring,
non-follows are silently ignored
Android WebRTC integration (amethyst/service/call):
- WebRtcCallSession wrapping Google WebRTC PeerConnection
- CallForegroundService for keeping calls alive in background
- IceServerConfig with default public STUN servers
- User-configurable TURN server support
Android UI (amethyst/ui/call):
- CallScreen with offering, connecting, connected, and ended states
- IncomingCallUI with accept/reject buttons
- ConnectedCallUI with mute, video toggle, speaker, and timer
- Call button added to 1-on-1 DM chat header
- ActiveCall route added to navigation
https://claude.ai/code/session_017hZm7yu7CzmcQgZGSaqSXS
The Guardian Project's arti-mobile-ex AAR has three problems:
1. No 16KB page-aligned binaries (required for Google Play)
2. ArtiProxy's stop()+start() causes state file lock conflicts
(lock is tied to TorClient object lifetime, released only on GC)
3. ~140MB AAR size
Replace with a custom JNI bridge built from Arti source, following
BitChat's proven approach:
Build tooling (tools/arti-build/):
- build-arti.sh: Clones official Arti, compiles with cargo-ndk
for ARM64 + x86_64, NDK 25+ for 16KB page alignment
- Cargo.toml: Minimal deps with size-optimized release profile
- src/lib.rs: Custom SOCKS5 proxy with proper lifecycle:
- initialize() creates TorClient once (holds state lock forever)
- startSocksProxy() binds port and accepts connections
- stopSocksProxy() aborts listener only (TorClient stays alive)
This cleanly separates "stop routing traffic" from "destroy client"
Kotlin side:
- ArtiNative.kt: JNI declarations + ArtiLogCallback interface
- TorService.kt: Uses ArtiNative directly, start() initializes +
starts proxy, stop() only stops proxy (no lock issues)
- TorManager.kt: Restored stop() calls for OFF/EXTERNAL modes
since our native stop is now safe
Removed: arti-mobile-ex dependency from build.gradle and version catalog
Native libraries must be built separately:
cd tools/arti-build && ./build-arti.sh
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
Replace Guardian Project's tor-android (C Tor) and jtorctl with
arti-mobile-ex, a Rust-based Tor implementation. This eliminates
the Android Service binding complexity in favor of an in-process
ArtiProxy object.
Key changes:
- TorService: Replace ServiceConnection to org.torproject.jni.TorService
with direct ArtiProxy.Builder/start/stop API. Bootstrap state detected
via log parsing (following BitChat's pattern).
- TorServiceStatus: Remove TorControlConnection field (Arti doesn't
support jtorctl control protocol).
- RelayProxyClientConnector: Remove DORMANT/ACTIVE/NEWNYM control
signals. Arti manages its own circuit lifecycle internally.
- Dependencies: Replace tor-android + jtorctl with arti-mobile-ex 1.2.3.
TorManager's external API (status/activePortOrNull StateFlows) and all
downstream consumers (DualHttpClientManager, TorSettings, UI) are
unchanged.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
Add support for NIP-77 (Negentropy Syncing) protocol messages using
the negentropy-kmp library for efficient set reconciliation between
client and relay.
New files:
- NegOpenCmd, NegMsgCmd, NegCloseCmd: Client-to-relay commands
- NegMsgMessage, NegErrMessage: Relay-to-client messages
- NegentropySession: Client-side reconciliation orchestrator
- NegentropyServerSession: Server-side reconciliation handler
- NegentropyManager: High-level sync manager with listener callbacks
Updated serializers:
- Jackson MessageSerializer/Deserializer for NEG-MSG, NEG-ERR
- Jackson CommandSerializer/Deserializer for NEG-OPEN, NEG-MSG, NEG-CLOSE
- Kotlin MessageKSerializer/CommandKSerializer for all NIP-77 types
Compatible with strfry relay's negentropy implementation via the
negentropy-kmp library which implements the same Protocol V1 spec.
https://claude.ai/code/session_01Dc6W1G1jURAAR9kzyVvk6g
Adds a MetadataStripper that removes GPS location, camera info, timestamps,
and other sensitive EXIF/metadata from images, videos, and audio files before
uploading. This protects user privacy by default.
- Creates MetadataStripper using AndroidX ExifInterface for images and
MediaMuxer for video/audio remuxing
- Adds "Strip location metadata" toggle to all upload dialogs (NewMediaView,
ChatFileUploadDialog) so users can disable stripping per upload
- Persists the setting in AccountSettings (stripLocationOnUpload, default true)
- Integrates stripping into UploadOrchestrator before compression
- Covers all upload paths: media posts, DM attachments, channel uploads,
profile pictures, list/channel metadata images
https://claude.ai/code/session_01974zpAMSRD9GeqBt9ax6XD
- Fixes incorrect aspect ratios of the AutoNonLazyGrid when multiple images exist for a single entry
- Adds Play buttons when the image fails to load so that people click and the post appears.
Remove Moko Resources dependency that blocks AGP 9.0 migration.
Migrate to built-in Compose Multiplatform Resources which is compatible
with the new KMP Android plugin.
- Remove moko-resources plugin and libraries from commons module
- Move strings.xml from moko-resources/ to composeResources/values/
- Update imports from SharedRes.strings.* to Res.string.*
- Add compose.components.resources dependency to desktopApp
- Configure resource package as com.vitorpamplona.amethyst.commons.resources
Closes#1675
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>