Goal (user-facing)
In-app voice calling between rider and driver, signaled and connected over Nostr. Use case: driver got lost finding the pickup, rider can't spot the driver at a busy curb, anything where text chat is too slow and an out-of-band phone call would otherwise require sharing real phone numbers — which the trusted-driver-but-not-PII model is built specifically to avoid.
This issue covers both Ridestr (rider Android) and Drivestr (driver Android) consumer-side integration of NostrCall, since they live in the same monorepo and consume the same SDK with mirrored authorization predicates.
Protocol home
The calling protocol is being designed as a standalone, publishable, cross-platform library:
github.com/variablefate/NostrCall — Rust core + UniFFI bindings, Swift shell for iOS, Kotlin shell for Android.
The full spec lives in SPEC.md. High-level architecture:
- Nostr for signaling (call invite/response/hangup via NIP-17 gift-wraps, three new event kinds)
- Hyperswarm DHT + holepunch for NAT traversal — no TURN servers, no signaling server
- WebRTC for media (Opus voice in v0; voice + optional video v0.5+)
- DTLS fingerprint binding signed in the Nostr-signed call offer, so a relay or DHT-level MITM cannot substitute its own media keys
Call authentication runs entirely through the existing Roadflare/Ridestr key-sharing scheme — gift-wraps are signed by the caller's Nostr identity and the canReceiveCall predicate enforces "caller is in my followed-and-key-shared list" against existing key state. No FCM, APNs, or third-party identity layer is involved in authentication.
This issue tracks the ridestr/drivestr consumer-side integration work — wiring up NostrCall once it's ready. The protocol design itself moves to that repo.
v0 lifecycle constraint: both apps in foreground
NostrCall v0 deliberately scopes itself to calls placed while both peers have the app in the foreground. This keeps v0 truly zero-infrastructure — no push notifier of any kind, no Apple/Google credentials anywhere. The Nostr subscription for call invites runs over the live WebSocket while the app is open; calls placed to a backgrounded or killed app surface as a missed-call indicator next time the user opens it.
Android is technically capable of always-on background subscriptions via a foreground service, but adopting that pattern for v0 would make the cross-platform UX asymmetric with the iOS sibling (roadflare-ios). Both platforms ship with the same foreground-only constraint for v0. If demand for backgrounded-receive surfaces post-launch, NostrCall Phase 9 adds the Android-specific pieces (ConnectionService + foreground service + FCM data-only push) symmetrically with the iOS pieces; the wire protocol does not change.
See SPEC.md §8.0 and §8.4` for the architectural rationale.
Consumer-side scope
When NostrCall reaches Phase 5 (v0 in-app call UX shipped on both platforms), integration in this monorepo is:
Ridestr (rider Android)
Drivestr (driver Android)
What's deferred from v0
- Video. Voice-only for the initial ship. Protocol supports it; we'll add it when a real use case appears.
- Group calls. Pairwise only.
- Call recording / transcription. Out of scope.
- Backgrounded-receive ringing. v0 only rings when the app is foreground. Calls to a backgrounded/killed app surface as missed-call indicators. NostrCall Phase 9 path exists; expand only on real user demand.
- Multi-device ringing. v0 rings the device that processed the gift-wrap first.
- System Phone-app integration. No ConnectionService in v0; in-app UI only.
Dependencies / ordering
- NostrCall Phase 0-5 must land before this issue is implementable. Track in the NostrCall repo's
SPEC.md §12.
- roadflare-ios#82 is the iOS sibling — same NostrCall SDK consumed from the Swift side. Once both ship Phase 5 integration, cross-platform calls (iOS rider ↔ Android driver, and Android rider ↔ iOS driver) work natively.
Status
- 🟢 Protocol spec drafted (NostrCall repo, May 2026)
- 🟢 v0 scoped to foreground-only (no push infrastructure required)
- 🟡 NostrCall implementation: not started; Phase 0 feasibility spike pending
- 🟡 ridestr/drivestr integration work: blocked on NostrCall Phase 5
Goal (user-facing)
In-app voice calling between rider and driver, signaled and connected over Nostr. Use case: driver got lost finding the pickup, rider can't spot the driver at a busy curb, anything where text chat is too slow and an out-of-band phone call would otherwise require sharing real phone numbers — which the trusted-driver-but-not-PII model is built specifically to avoid.
This issue covers both Ridestr (rider Android) and Drivestr (driver Android) consumer-side integration of NostrCall, since they live in the same monorepo and consume the same SDK with mirrored authorization predicates.
Protocol home
The calling protocol is being designed as a standalone, publishable, cross-platform library:
github.com/variablefate/NostrCall — Rust core + UniFFI bindings, Swift shell for iOS, Kotlin shell for Android.
The full spec lives in
SPEC.md. High-level architecture:Call authentication runs entirely through the existing Roadflare/Ridestr key-sharing scheme — gift-wraps are signed by the caller's Nostr identity and the
canReceiveCallpredicate enforces "caller is in my followed-and-key-shared list" against existing key state. No FCM, APNs, or third-party identity layer is involved in authentication.This issue tracks the ridestr/drivestr consumer-side integration work — wiring up NostrCall once it's ready. The protocol design itself moves to that repo.
v0 lifecycle constraint: both apps in foreground
NostrCall v0 deliberately scopes itself to calls placed while both peers have the app in the foreground. This keeps v0 truly zero-infrastructure — no push notifier of any kind, no Apple/Google credentials anywhere. The Nostr subscription for call invites runs over the live WebSocket while the app is open; calls placed to a backgrounded or killed app surface as a missed-call indicator next time the user opens it.
Android is technically capable of always-on background subscriptions via a foreground service, but adopting that pattern for v0 would make the cross-platform UX asymmetric with the iOS sibling (roadflare-ios). Both platforms ship with the same foreground-only constraint for v0. If demand for backgrounded-receive surfaces post-launch, NostrCall Phase 9 adds the Android-specific pieces (ConnectionService + foreground service + FCM data-only push) symmetrically with the iOS pieces; the wire protocol does not change.
See
SPEC.md§8.0 and §8.4` for the architectural rationale.Consumer-side scope
When NostrCall reaches Phase 5 (v0 in-app call UX shipped on both platforms), integration in this monorepo is:
Ridestr (rider Android)
canReceiveCallpredicate — "caller is a followed driver AND has shared a roadflare-key with us"NostrCallRinging(call)composable at the root activity layer (or implement custom UI driven bycall.incomingCallsFlow)<uses-permission android:name="android.permission.RECORD_AUDIO" />and<uses-permission android:name="android.permission.INTERNET" />Drivestr (driver Android)
canReceiveCallpredicate mirrored — "caller is a followed rider AND has shared a roadflare-key with us"What's deferred from v0
Dependencies / ordering
SPEC.md§12.Status