A mobile Bitcoin Lightning wallet built with Expo/React Native, connecting via Nostr Wallet Connect (NWC) with Nostr social features.
- Connect any Lightning wallet via NWC (Nostr Wallet Connect)
- On-chain Bitcoin wallets (watch-only via xpub, or hot wallets via mnemonic)
- Transfer funds between wallets (LN-to-LN, LN-to-on-chain, on-chain-to-LN, on-chain-to-on-chain)
- Send payments by scanning QR codes or pasting invoices
- Send to lightning addresses (user@domain) via LNURL-pay
- Send to on-chain addresses (BIP-21 URI support with amount)
- NIP-57 zaps to Nostr contacts
- Receive payments with QR code generation (optional amount for BIP-21)
- Real-time balance display with fiat conversion
- Transaction history
- Nostr identity login (nsec or Amber signer on Android)
- Friends tab with Nostr contacts and phone contacts
- Follow/unfollow Nostr contacts (kind 3 event publishing)
- Add friends by pasting npub or scanning QR code
- Contact profile cards with deep linking to Nostr apps
- QR code sharing for npub and Lightning address
- Secure credential storage (expo-secure-store)
- Explore tab: discover Bitcoin merchants nearby (BTC Map / OpenStreetMap),
opt-in geofence alerts when you walk past one (#467), hunt for hidden
LNURL-withdraw "Piggies" stashed on NFC tags or QR codes (#468), and find
Bitcoin meetups, conferences, and workshops via NIP-52 calendar events
(kind 31923 β read-only, with native Add-to-Calendar via
expo-calendar). Hunt Piggies adopt treasures.to's NIP-GC (kind 37516listings,kind 7516found-logs,kind 1111NIP-22 comments) and tag the listing with a NIP-32com.lightningpiggy.app / payout-lnurl-wlabel so LP renders the π· claim UX on top while generic geocaching clients render the cache normally. The LNURL bearer token never goes on the public relay event β it lives only on the physical NFC tag / QR (the access control) and in the hider's local SecureStore. Hider can update location / hint photo / cooldown / expiry by re-publishing with the samedtag (PRE semantics). See docs/PROTOCOLS.adoc β "Hunt: Lightning extension to NIP-GC" for the full tag schema, and docs/SOLUTION_DESIGN.adoc β "Explore tab" for the BTC Map + Nostr cache architecture, bbox-driven list filtering, and offline cold-start guarantees.
Lightning Piggy Mobile implements the following open standards. See docs/STANDARDS.adoc for usage details and source references.
| Standard | Name |
|---|---|
| BIP-21 | URI Scheme (bitcoin: with ?amount=) |
| BIP-32 | Hierarchical Deterministic Wallets |
| BIP-39 | Mnemonic Seed Phrases |
| BIP-84 | Native SegWit HD Wallets (bc1q...) |
| BIP-327 | MuSig2 Schnorr Key Aggregation |
| BIP-340 | Schnorr Signatures |
| BIP-341 | Taproot (SegWit v1) |
| Standard | Name |
|---|---|
| NIP-01 | Basic Protocol (relays, events, subscriptions) |
| NIP-04 | Encrypted Direct Messages (legacy β inbound read only; outbound migrated to NIP-17 in #334) |
| NIP-05 | DNS Identity Verification |
| NIP-14 | Subject Tag (used as group-chat name) |
| NIP-17 | Private Direct Messages β canonical 1:1 and multi-recipient group chat (sealed via NIP-44 + gift-wrapped per recipient) |
| NIP-19 | Bech32-encoded Entities (npub, nsec) |
| NIP-20 | Command Results (OK messages) |
| NIP-21 | nostr: URI Scheme |
| NIP-22 | Comments (kind 1111 β used by NIP-GC for DNF / maintenance / archived cache statuses) |
| NIP-32 | Labels (L namespace + l value tags β used to mark Hunt caches as Lightning-payout via the com.lightningpiggy.app / payout-lnurl-w label) |
| NIP-40 | Expiration Timestamp (auto-retire Hunt cache listings) |
| NIP-44 | Encrypted Payloads v2 (used by NIP-17 sealing) |
| NIP-47 | Nostr Wallet Connect (NWC) |
| NIP-52 | Calendar Events (kind 31923 time-based β read-only consumer for the Events sub-screen) |
| NIP-55 | Android Signer (Amber) |
| NIP-57 | Lightning Zaps |
| NIP-59 | Gift Wrap (used by NIP-17 to mask sender + metadata) |
| NIP-65 | Relay List Metadata |
| NIP-94 | File Metadata |
| NIP-99 | Classified Listings (kind 30402 β read-only consumer for the Robotechy Bag Charm purchase link on the Hide-a-Piglet flow) |
| NIP-GC (draft, on Nostr) | Geocaching Events (kinds 37516 / 7516 / 1111 / 7517 β used by the Hunt feature, with an com.lightningpiggy.app NIP-32 label marker for Lightning-payout caches) |
Group state for client-side group chat is propagated via a parameterised-replaceable
kind:30200 event (custom to this client; receivers reconcile by groupId + created_at).
Not a NIP β see src/services/nostrService.ts (GROUP_STATE_KIND) for the schema.
| Standard | Name |
|---|---|
| LUD-03 | LNURL-withdraw β used by the Hunt feature to claim Piggies |
| LUD-16 | Lightning Address (user@domain β LNURL-pay endpoint) |
| LUD-17 | URI scheme prefixes (lnurlw://, lnurlp://, β¦) β accepted as input alongside bech32 LNURL1β¦ |
| Standard | Name |
|---|---|
| BOLT-11 | Lightning Invoice Encoding |
| LNURL / LUD-16 | Lightning Address Protocol (user@domain) |
On top of these, Lightning Piggy Mobile uses the Boltz v2 submarine swap API for trustless Lightning β on-chain transfers.
Lightning Piggy is an Expo / React Native app (SDK 55, RN 0.83) that runs from a custom dev-client β it ships custom native modules, so Expo Go is not used. Lightning payments ride on Nostr Wallet Connect (NWC); the social layer is Nostr (encrypted DMs, zaps, contacts, geo-caches). A fuller write-up lives in docs/ARCHITECTURE.adoc.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Screens & Components (src/screens, src/components) β
β Home Β· Messages Β· Explore Β· Friends + Account drawer β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Navigation (src/navigation/AppNavigator.tsx) β
β 4 tabs β Explore stack Β· account drawer Β· root stack β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Contexts (src/contexts) Hooks (src/hooks) β
β WalletContext Β· NostrContext Β· UI-facing per-feature β
β Groups Β· TrustGraph Β· LiveLoc logic β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Services (src/services) β by domain β
β Nostr (nostrService, dm*, places) Β· Wallet (nwcService, β
β onchainService/BDK, lnurl, boltz) Β· location/geofence Β· β
β media upload Β· NFC Β· notifications β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Persistence β
β SQLCipher DM store (op-sqlite) Β· expo-secure-store (keys) Β· β
β AsyncStorage caches (per-account namespaced) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Native (modules/, plugins/) β
β amber-signer (NIP-55 IPC) β the only native module Β· β
β Expo config plugins (NFC, Amber queries, fg-service perms) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β
Nostr relays NWC wallet svc Electrum / Boltz
(DMs, zaps, (pay_invoice, (on-chain sync,
contacts, caches) get_balance) LNβon-chain swaps)
Layers. Presentation (screens/components) sits over React Navigation; long-lived state lives in contexts (WalletContext for wallets/balances/transactions, NostrContext for identity/signer/relays/DMs), which compose per-responsibility hooks and a large, mostly-stateless service layer grouped by domain. Persistence is split three ways by sensitivity: an encrypted SQLCipher DM store, expo-secure-store for keys/secrets, and per-account-namespaced AsyncStorage caches. One Android-only native module (amber-signer, the NIP-55 Amber IPC bridge β the only Expo native module that ships) and a set of Expo config plugins round out the stack. Background DM delivery rides expo-background-task (Android WorkManager / iOS BGTaskScheduler), not a persistent native service.
Key data flows.
- Sending a DM β build a NIP-17 chat rumor (kind 14) β seal (kind 13) β gift-wrap (kind 1059) with NIP-44 β publish to the peer's and own relays. Inbound wraps are unwrapped once and stored plaintext in the encrypted SQLite DB. When the app is closed on Android, a periodic
expo-background-task(WorkManager, ~15-min floor) does detect-and-ping β it notices new inbound traffic and fires a generic notification without decrypting; a persistent realtime relay foreground service is only scaffolded (manifest permissions viawithForegroundService.js) and not yet implemented. See docs/architecture/notifications.adoc. - Paying over NWC β a scanned invoice (or an LNURL-pay / Lightning address resolved to a BOLT-11) is sent to the wallet service as an encrypted NIP-47
pay_invoicerequest over the NWC relay; the encrypted response updates balance and history. Zaps (kind 9734 request β 9735 receipt) ride the same rail. - Publishing a geo-cache ("Piglet") β a NIP-GC listing (kind 37516) is published with a NIP-32 payout label and a stable
dtag; the LNURL-withdraw bearer stays on the physical NFC tag / QR and in the hider's secure store, never on the public event. Claims record a found-log (kind 7516).
See docs/ARCHITECTURE.adoc for the module map, the full Nostr event-kind reference, signer options (nsec / Amber; NIP-46 planned), and persistence detail.
- Node.js (v18+)
- Expo CLI
- An Android device or emulator
- A Lightning wallet that supports NWC (e.g. Alby, LNbits)
git clone https://github.com/BenGWeeks/lightning-piggy-mobile
cd lightning-piggy-mobile
npm installThis project uses custom native modules (Amber signer), so it requires a dev client build rather than Expo Go.
# First time: build and install the dev client
npx expo run:android
# Subsequent runs: start Metro (connects to the dev client)
npm start
# Press 'a' to open on a connected Android deviceNote: Always use
npm start(notnpx expo start) β the start script includes--dev-clientwhich is required for custom native modules. Usingnpx expo startdirectly will launch in Expo Go mode and show "Something went wrong".
Optional keys are read from .env at the repo root (gitignored) and inlined into the bundle at build time via app.config.ts / Metro's EXPO_PUBLIC_* handling.
| Variable | Used by | Notes |
|---|---|---|
EXPO_PUBLIC_GIPHY_API_KEY |
In-conversation GIF picker | Free key from developers.giphy.com/dashboard. Restrict it to the Android package / iOS bundle ID in the GIPHY dashboard β keys are public by design, not a secret. When unset, the "Send GIF" row is hidden from the attach menu and nothing else breaks. |
After changing .env, restart Metro (npm start) so the new value gets inlined. No native rebuild is required for EXPO_PUBLIC_* changes.
# Generate the native Android project
npx expo prebuild --platform android
# Build the APK
cd android && ./gradlew assembleRelease
# Install on connected device
adb install app/build/outputs/apk/release/app-release.apknpm install -g eas-cli
eas login
eas build --platform android --profile previewsrc/
components/ # Reusable UI components (SendSheet, ContactProfileSheet, etc.)
contexts/ # React contexts (WalletContext, NostrContext)
navigation/ # React Navigation setup
screens/ # App screens (Home, Earn, Learn, Friends, Account)
services/ # Business logic (NWC, LNURL, Nostr, contacts)
styles/ # Theme and shared styles
types/ # TypeScript type definitions
assets/ # Images and icons
modules/ # Custom native Expo modules (Amber signer)
plugins/ # Expo config plugins
The Explore tab pulls in a small set of Expo modules beyond the wallet + Nostr core. All are listed in docs/PACKAGES.adoc; the Explore-specific additions are:
| Package | Used by | Why |
|---|---|---|
react-native-webview |
ExploreMiniMap, MapScreen |
Leaflet WebView for OSM tile rendering β no Google Maps API key, no billing setup. |
expo-asset + expo-file-system + expo-sharing |
HuntCreateScreen "Save STL" |
Ships the 188 KB Piggy Bag Charm STL inside the APK, copies it out on tap, and shares via the system sheet. |
expo-calendar |
EventDetailScreen "Add to Calendar" |
Native calendar insert with lazy permission prompt. |
expo-notifications |
Geofence "Nearby merchants" feature (#467) | Foreground pings when the user walks past a Bitcoin merchant. |
The Explore tab surfaces live under docs/screenshots/explore/:
01-tab-bar-home.pngβ bottom tab bar with the new Explore tab02-explore-hub.png/02b-explore-hub-scrolled.pngβ Lessons / Map / Hunt / Events rails03-lessons.png04-map-merchants-and-caches.pngβ full Leaflet view05-hunt-hub.png,06-hunt-create.png,06b-hunt-nfc-write.png,07-hunt-discover.png08-events.png09-hunt-piggy-detail.png/09b-hunt-piggy-detail-scrolled.png10-places-list.png,11-place-detail.png
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.




