Zero-infrastructure crash reporting — no server to run, no SaaS to pay for.
Bugstr delivers crash reports via NIP-17 encrypted direct messages with user consent. Reports auto-expire after 30 days.
| Platform | Directory | Tested |
|---|---|---|
| Android/Kotlin | android/ |
✅ Zapstore |
| Electron | electron/ |
🐹 Guinea pigs needed |
| Flutter/Dart | dart/ |
🐹 Guinea pigs needed |
| Rust | rust/ |
🐹 Guinea pigs needed |
| Go | go/ |
🐹 Guinea pigs needed |
| Python | python/ |
🐹 Guinea pigs needed |
| React Native | react-native/ |
🐹 Guinea pigs needed |
Crash → Cache locally → App restart → Show consent dialog → User approves → Send encrypted DM (expires in 30 days)
- Crash occurs - Exception handler captures stack trace
- Local cache - Report saved to disk (no network)
- User consent - Dialog shows on next app launch
- NIP-17 DM - Encrypted, gift-wrapped message sent to developer
- Auto-expiration - Report deleted from relays after 30 days
All SDKs use the same default relay list, chosen for reliability:
| Relay | Max Event Size | Max WebSocket | Notes |
|---|---|---|---|
wss://relay.damus.io |
64 KB | 128 KB | strfry defaults |
wss://relay.primal.net |
64 KB | 128 KB | strfry defaults |
wss://nos.lol |
128 KB | 128 KB | Fallback relay |
Note: Most relays use strfry defaults (64 KB event size, 128 KB websocket payload). The practical limit for crash reports is ~60 KB to allow for gift-wrap envelope overhead.
You can override these defaults via the relays configuration option in each SDK.
Crash reports are subject to relay message size limits (see NIP-11 max_message_length).
| Relay Limit | Compatibility |
|---|---|
| 64 KB | ~99% of relays |
| 128 KB | ~90% of relays |
| 512 KB+ | Major relays only |
Practical limit: Keep compressed payloads under 60 KB for universal delivery (allows ~500 bytes for gift-wrap envelope overhead).
| Payload Size | Behavior |
|---|---|
| < 1 KB | Sent as plain JSON |
| ≥ 1 KB | Compressed with gzip, base64-encoded |
Large payloads are wrapped in a versioned envelope:
{
"v": 1,
"compression": "gzip",
"payload": "<base64-encoded-gzip-data>"
}Stack traces are automatically truncated to fit within limits (default: 200 KB before compression). The receiver CLI/WebUI automatically detects and decompresses payloads.
Gzip typically achieves 70-90% reduction on stack traces due to their repetitive text patterns:
| Original Size | Compressed | Reduction |
|---|---|---|
| 10 KB | ~1-2 KB | ~80-90% |
| 50 KB | ~5-10 KB | ~80-90% |
| 200 KB | ~20-40 KB | ~80-85% |
With gzip compression (70-90% reduction), most crash reports fit well within the 64 KB strfry default limit. For maximum compatibility, keep compressed payloads under 60 KB.
All implementations use these NIPs:
- NIP-17 - Private Direct Messages (kind 14 rumors)
- NIP-44 - Versioned Encryption (v2, XChaCha20-Poly1305)
- NIP-59 - Gift Wrap (rumor → seal → gift wrap)
- NIP-40 - Expiration Timestamp
Per NIP-17, rumors (kind 14) must include:
id- SHA256 hash of[0, pubkey, created_at, kind, tags, content]sig: ""- Empty string (not omitted)
Some clients (e.g., 0xchat) reject messages missing these fields.
The test-vectors/ directory contains JSON test cases for NIP-17. All platform implementations should validate against these vectors.
Release builds typically use code obfuscation/minification, producing stack traces with mangled names and memory addresses instead of readable function names and line numbers.
Current status: Symbolication tooling is not yet implemented. Crash reports contain raw stack traces as captured.
Planned approach:
- Store mapping files (ProGuard, dSYM, sourcemaps) locally or in your CI
- Use platform-specific tools to symbolicate:
- Android:
retracewith ProGuard mapping - iOS/macOS:
atosorsymbolicatecrashwith dSYM - JavaScript: Source map support in browser devtools
- Flutter:
flutter symbolizewith app symbols
- Android:
Contributions welcome for automated symbolication in the receiver CLI/WebUI.
See AGENTS.md for contributor guidelines covering:
- Documentation requirements
- Commit conventions
- NIP implementation notes