A non-custodial Bitcoin Lightning wallet for iOS and Android.
- ⚡ Lightning payments — send and receive over Lightning
- 🔗 On-chain and Spark — receive to a Bitcoin address or a Spark address, send to both
- 🔤 LNURL-Pay — pay Lightning addresses and LNURL-Pay links
- 🔗 Payment links — pay
lightning:,bitcoin:(BIP 21 / BIP 321) andstarr://sendlinks from other apps, then go back to the calling app. See docs/DEEP_LINKS.md - 🕓 History — payment list with filters, pagination and a detail screen
- 🗺 Merchant map — nearby places that accept Bitcoin, from BTC Map
- 🔐 Non-custodial — the 24-word phrase stays in platform secure storage
- 📱 iPhone and iPad — responsive layouts, light and dark theme
Starr uses the Breez SDK Spark
(@breeztech/breez-sdk-spark-react-native) for all wallet operations. The SDK
runs in the app, so there is no node to host and no server component.
app/** expo-router screens
→ src/stores/walletStore.ts zustand: shared wallet state
→ src/services/breez/BreezService.ts the only file that imports the SDK
→ @breeztech/breez-sdk-spark-react-native
BreezService translates the SDK tagged unions into the plain domain types in
src/types/wallet.ts. Screens use only those types. Amounts are bigint
satoshis everywhere.
- React Native 0.88 with Expo SDK 58
- Breez SDK Spark for Lightning, on-chain and Spark payments
- Zustand for state, Expo Router for navigation
- TypeScript
- Node.js 22.13+ or 24.3+
- Xcode (iOS) or Android Studio (Android)
- An EAS account for cloud builds
The Breez SDK is a native module. Expo Go cannot run this app — you must install a development client build first.
npm install
npm run ios # expo run:ios: builds the native project, then runs it
npm run android # expo run:android
npm start # Metro, for a device that already has the dev clientOr build the dev client in the cloud:
eas build --profile development --platform iosios/ and android/ are generated by expo prebuild and are gitignored.
Change app.json instead of the native projects. Any change to app.json
plugins or to a native dependency needs a new build, not a Metro reload.
Create a .env file in the root directory:
# Required. Without an API key BreezService.initialize throws.
EXPO_PUBLIC_BREEZ_API_KEY=your_breez_api_key
EXPO_PUBLIC_BREEZ_NETWORK=mainnet # mainnet | regtest
# Optional
EXPO_PUBLIC_BREEZ_WORKING_DIR=/absolute/path
EXPO_PUBLIC_BREEZ_SYNC_INTERVAL_SECS=60
# Android map tiles. Read at build time by app.config.js, not at runtime.
GOOGLE_MAPS_API_KEY=your_google_maps_api_keyAll wallet variables are read in one place, src/config/breez.ts.
GOOGLE_MAPS_API_KEY goes into AndroidManifest.xml as
com.google.android.geo.API_KEY. The key needs the Maps SDK for Android
API, active billing, and — if it has Android application restrictions — the
package name com.starrwallet together with the SHA-1 of the signing
certificate. A key that fails these checks gives a black map, not an error.
.env is gitignored and is not uploaded to EAS. For cloud builds, set the same
variables as EAS environment variables (eas env:list --environment production).
eas build --platform android --profile preview # APK, internal distribution
eas build --platform android --profile production # AAB for the store
eas build --platform ios --profile productionA push to main starts the production build for both platforms through
.eas/workflows/create-production-builds.yml.
To start the same workflow by hand:
eas workflow:run .eas/workflows/create-production-builds.yml --ref mainnpx tsc --noEmit # type check
npm run lint # eslintnpm test calls jest, but the repository has no jest configuration and no
test files. Use the two commands above, plus
docs/QA_REGRESSION_CHECKLIST.md for manual
device testing.
StarrWallet/
├── app/ # Expo Router screens
│ ├── (tabs)/ # Wallet, History, Scan, Map, Settings
│ ├── onboarding/ # Create, import, backup
│ ├── payment/[id].tsx # Payment detail
│ └── send.tsx, receive.tsx, scan.tsx, delete-wallet.tsx
├── src/
│ ├── components/ # layout/, map/, ui/, wallet/
│ ├── config/ # breez.ts: all wallet env vars
│ ├── contexts/ # theme and colors
│ ├── hooks/ # useResponsive and others
│ ├── services/
│ │ ├── breez/ # BreezService: the only SDK consumer
│ │ ├── btcmap/ # BTC Map API client
│ │ └── keychain/ # KeychainService: seed phrase storage
│ ├── stores/ # walletStore, walletSession, sendStore, onboardingStore
│ ├── theme/ # colors, spacing, typography, layout
│ ├── types/ # domain types
│ └── utils/ # formatting and helpers
├── modules/ # local Expo modules
├── docs/ # payment status, QA checklist, deep links
└── assets/ # icons and splash images
The 24-word phrase (256 bits of entropy) is the only secret. It is stored with
expo-secure-store and requireAuthentication: true, so every read shows the
native biometric or passcode prompt. iOS uses Keychain Services, Android uses
the Keystore.
AuthGate wraps the app: it shows a privacy overlay when the app becomes
inactive and re-authenticates on a real background-to-active transition.
The phrase is the backup. There is no cloud backup and no channel state to back up — Spark restores the wallet from the phrase alone.
- Never share the recovery phrase
- Keep the 24 words offline, in a safe place
- The phrase is shown once, during onboarding
- iOS:
com.starwallet - Android:
com.starrwallet
See CONTRIBUTING.md.
MIT License — see LICENSE.
- Website: starr.app
- Support: support@starr.app
- Twitter: @StarrWallet
Built with ⚡ by the Starr team