nospeak is a decentralized Nostr chat client for secure, private messaging. It is easy to use but has state of the art end-to-end encryption without metadata leakage.
Install on Android from F-Droid, Obtainium or Zapstore or use the Progressive Web App on https://nospeak.chat
- 1-on-1 Conversations — End-to-end encrypted direct messages using the NIP-17 gift-wrap protocol with no metadata leakage
- Group Chats — Create named group conversations with multiple participants, all fully encrypted
- Emoji Reactions — React to messages with emoji; reactions are privacy-wrapped just like messages
- Reply & Quote — Reply to specific messages in a conversation
- Message Search — Search through your chat history
- Markdown — Bold, italic, lists, and code formatting in messages
- Chat Export — Export conversation history
- End-to-End Encrypted Voice Calls — Peer-to-peer WebRTC voice calls signaled over Nostr via NIP-AC ephemeral gift wraps (kind 21059, inner kinds 25050–25054). Voice-only, 1-on-1.
- Native Android Stack — On Android the entire call lifecycle (WebRTC peer connection, microphone capture, audio routing, signaling, ringback, lockscreen UI) runs in native Java/Kotlin. The JavaScript layer never touches an
RTCPeerConnectionon Android, so calls work even when the WebView is backgrounded or paused. - Lockscreen Accept — Incoming calls show a full-screen ringer over the keyguard. Accept routes through
IncomingCallActivity→ foreground service → nativeActiveCallActivitywithout going through the WebView. - Multi-Device Awareness — When you accept or decline a call on one device, the kind 25051 / 25054 self-wraps surface "answered elsewhere" / "declined elsewhere" on your other devices and stop their ringers.
- Follow-Gated Incoming — Incoming offers from non-followed pubkeys are dropped before ringing.
- Call History — Each call (ended, missed, declined, no-answer, failed, busy, cancelled) authors a kind 1405 chat-history rumor that renders inline in the conversation.
- Background Messaging Service (Android) — A permanent foreground service stays connected to your relays so you never miss a message, even when the app is closed
- Survives Reboots — The service restarts automatically after device reboots or app updates
- Web Push Notifications — Get notified of new messages in the browser via PWA notifications
- Encrypted Images & Videos — Media is encrypted client-side with AES-256-GCM before upload to Blossom servers
- Voice Messages — Record and send voice messages with waveform visualization (up to 3 minutes)
- Location Sharing — Share your GPS location as an interactive map
- File Attachments — Send any file type, fully encrypted
- Link Previews & YouTube Embeds — Rich URL previews with Open Graph metadata
- Android Share Target — Share images, videos, and text directly from other apps into nospeak
- Android Media Cache — Save received photos and videos to your gallery
- NIP-17 Gift Wrap Protocol — Triple-layer encryption (Gift Wrap, Seal, Rumor) with randomized timestamps to prevent timing analysis
- NIP-44 Encryption — State-of-the-art versioned encryption for all message content
- Encrypted Contact Lists — Your contact list is encrypted before syncing to relays
- Android Keystore — When using nsec to login, secret keys stored in hardware-backed secure storage on Android
- App Lock (PIN) — Optional 4-digit PIN to lock the app; auto-locks when backgrounded
- No Central Server — All data lives locally on your device and on Nostr relays you choose
- Android App — Native Android app with OS integration (background service, share targets, haptics)
- Progressive Web App — Use in any modern browser with offline support
- External Signer Support — Login with NIP-07 browser extensions (nos2x, Alby) or NIP-55 Android signers (Amber)
- Search & Add Contacts — Find users by name, npub, or NIP-05 address
- QR Code Sharing — Share your profile or scan others via QR codes
- NIP-05 Verification — Verified identity badges on profiles
- Favorites — Star important messages, synced across devices
- Archives — Archive old conversations to keep your inbox clean
- Unread Badges — Per-conversation unread message counts
- Custom Relay Configuration — Choose which relays you send and receive messages on
- Relay Health Monitoring — Real-time connection status and auth state per relay
- Auto-Reconnect — Automatic reconnection with exponential backoff
- 21 Languages — English, العربية, বাংলা, Deutsch, Español, فارسی, Français, עברית, हिन्दी, Italiano, 日本語, 한국어, Nederlands, Polski, Português, Русский, ไทย, Türkçe, اردو, Tiếng Việt, 简体中文 — with full RTL support
- Theme Support — System, light, dark, and Cypher (cyberpunk neon) mode with Catppuccin color palette
- Profile Customization — Set your name, avatar, banner, bio, and Lightning address
- Node.js 18+
- npm or yarn package manager
# Clone the repository
git clone https://github.com/your-org/nospeak.git
cd nospeak
# Install dependencies
npm install
# Start development server
npm run devsrc/
├── lib/
│ ├── components/ # Svelte components
│ ├── core/ # Core business logic
│ ├── db/ # Database layer (Dexie)
│ ├── stores/ # State management
│ └── utils/ # Utility functions
├── routes/ # SvelteKit pages and API routes
└── app.html # Main HTML template
- ConnectionManager: Handles Nostr relay connections and subscriptions
- MessagingService: Manages message sending/receiving with encryption
- AuthService: Handles user authentication with Nostr keys
- ProfileService: Manages user profiles and metadata
- MessageRepository: Local database storage for messages
- VoiceCallBackend (
src/lib/core/voiceCall/): Platform-split voice-call backend. The factory infactory.tsreturnsVoiceCallServiceWeb(browser-sideRTCPeerConnection+ JS NIP-AC senders) on PWA/desktop orVoiceCallServiceNative(thin proxy to the Android plugin) on Android. UI components subscribe to a single Svelte store (voiceCallState) regardless of platform.
On Android the call path is fully native to keep media + signaling alive when the WebView is paused:
VoiceCallForegroundService—phoneCall-typed FGS that hosts the call. RoutesACTION_INITIATE_NATIVE/ACTION_ACCEPT_NATIVE/ACTION_HANGUP_NATIVE/ACTION_AWAIT_UNLOCKto the manager and owns theActiveCallActivitylaunch.NativeVoiceCallManager— single-threaded state machine wrappingorg.webrtc.PeerConnection. Owns the ICE buffer, offer/ICE timeouts, mute/duration tracking, the post-ENDED IDLE reset (so back-to-back calls work), and the call-history decision tree.IncomingCallActivity— full-screen lockscreen ringer launched by the FSI onIncomingCallNotification. Heads-up Accept routes through here viaACTION_AUTO_ACCEPTso there's a single accept code path.ActiveCallActivity— in-call surface (peer name, duration timer, mute / speaker / hangup buttons).singleTask,showWhenLocked,turnScreenOn. Bound to the FGS via a localIBinderand subscribes to the manager'sUiListener.NativeBackgroundMessagingService— already-existing DM service is the relay socket owner. NIP-AC kinds (25050–25054 wrapped in 21059) are Schnorr-verified and dispatched into the manager directly; the JSMessaging.tsskips them on Android.AndroidVoiceCallPlugin— Capacitor bridge: 7 methods (initiate / accept / decline / hangup / toggleMute / toggleSpeaker / notifyUnlockComplete) and 6 events (callStateChanged, durationTick, callError, muteStateChanged, callHistoryWriteRequested, callHistoryRumorRequested).
PIN-locked nsec users hit a deferred-accept flow: IncomingCallActivity persists the pending call, brings the FGS up in await-unlock mode, and routes through MainActivity so the JS unlock screen can collect the PIN. After successful unlock the JS layer calls notifyUnlockComplete, the FGS broadcasts nospeak.ACTION_UNLOCK_COMPLETE, and the call resumes natively.
OpenSpec: voice-calling capability is described in openspec/specs/voice-calling/spec.md. The implementation arc is archived under openspec/changes/archive/2026-05-01-add-native-voice-calls/ and openspec/changes/archive/2026-05-01-migrate-voice-calling-to-nip-ac/.
- SvelteKit: Modern web framework with SSR support
- TypeScript: Type-safe development
- Dexie: IndexedDB wrapper for local storage
- Nostr Tools: Nostr protocol implementation
- Tailwind CSS: Utility-first CSS framework
- Stream WebRTC Android (
io.getstream:stream-webrtc-android): WebRTC bindings for the native Android voice-call stack
Create .env file for local development (restart the server to apply changes):
# Runtime-configurable defaults (embedded via SSR at page load)
# Relays MUST use wss:// and servers MUST use https://
NOSPEAK_DISCOVERY_RELAYS=wss://nostr.data.haus,wss://relay.damus.io,wss://nos.lol,wss://relay.primal.net,wss://purplepag.es,wss://user.kindpag.es,wss://profiles.nostr1.com,wss://directory.yabu.me
NOSPEAK_DEFAULT_MESSAGING_RELAYS=wss://nostr.data.haus,wss://nos.lol,wss://relay.damus.io
NOSPEAK_SEARCH_RELAY=wss://relay.nostr.band
NOSPEAK_BLASTER_RELAY=wss://sendit.nosflare.com
NOSPEAK_DEFAULT_BLOSSOM_SERVERS=https://blossom.data.haus,https://blossom.primal.net
NOSPEAK_WEB_APP_BASE_URL=https://nospeak.chatThe Node server reads these values from process.env at startup and embeds them in the initial page via SSR. Update the environment values and recreate the container to apply changes.
services:
nospeak:
image: nospeak:latest
environment:
NOSPEAK_DISCOVERY_RELAYS: "wss://nostr.data.haus,wss://relay.damus.io,wss://nos.lol,wss://relay.primal.net,wss://purplepag.es,wss://user.kindpag.es,wss://profiles.nostr1.com,wss://directory.yabu.me"
NOSPEAK_DEFAULT_MESSAGING_RELAYS: "wss://nostr.data.haus,wss://nos.lol,wss://relay.damus.io"
NOSPEAK_SEARCH_RELAY: "wss://relay.nostr.band"
NOSPEAK_BLASTER_RELAY: "wss://sendit.nosflare.com"
NOSPEAK_DEFAULT_BLOSSOM_SERVERS: "https://blossom.data.haus,https://blossom.primal.net"
NOSPEAK_WEB_APP_BASE_URL: "https://nospeak.chat"Apply changes:
docker compose up -d --force-recreateDefault relays are configured, but users can add custom relays in settings. The app automatically:
- Connects to multiple relays for redundancy
- Handles connection failures with retry logic
- Manages subscription optimization
- All messages are end-to-end encrypted using Nostr's NIP-44
- Private keys never leave the user's device
- Profile metadata is publicly shared as per Nostr protocol
- Local IndexedDB for message history and profiles
- No server-side storage of private data
Nospeak can be packaged as a native Android application using Capacitor.
- Node.js 18+
- Java 17 (for recent Android Gradle plugin versions)
- At least one Android emulator or physical device (Android 8.0 / API 26 or newer)
# Install dependencies
npm install
# Build web assets and sync to Android project
npm run build:android
# Build an unsigned APK
cd android && ./gradlew clean :app:assembleDebugThe Capacitor configuration (capacitor.config.ts) is set to use the SvelteKit build/android directory as webDir, so the Android app loads the bundled nospeak UI from local assets.
# Build image
docker build -t nospeak .
# Run container
docker run -p 5173:5173 nospeak# Build for production
npm run build
# Deploy build/ directory to your static host
rsync -av build/ user@server:/var/www/nospeak/Nospeak Web implements the following NIPs (Nostr Implementation Proposals):
- NIP-01: Basic Nostr event and client protocol
- NIP-17: Encrypted direct messages and messaging relays
- NIP-19: bech32-encoded entities for keys and identifiers
- NIP-05: Mapping Nostr keys to DNS-based internet identifiers
- NIP-42: Authentication to relays
- NIP-50: Search relays for contact discovery
- NIP-65: Relay list metadata for messaging/mailbox relays
- NIP-25: Reaction events for emoji responses on messages
- NIP-44: Encrypted payloads for direct messages
- NIP-59: Gift wrapper events for DM and media delivery
- NIP-98: HTTP-authenticated media uploads
- NIP-AC: P2P voice calls over WebRTC (signaling kinds 25050–25054 wrapped in ephemeral kind 21059 gift wraps; voice-only, 1-on-1; multi-device "answered/rejected elsewhere"; follow-gated incoming ringing)
- NIP-07: Browser extension signer integration
- NIP-55: Android native signer integration (Amber and similar)
Copyright (C) 2026 psic4t
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.