System Architecture
Sovran is a React Native mobile wallet built on Expo, designed around the Cashu ecash protocol with deep Nostr integration. The architecture follows a layered approach with clear separation of concerns.Technology Stack
Core Framework- React Native 0.83 with Expo SDK 55 for cross-platform mobile development
- Expo Router for file-based routing and navigation
- TypeScript for type safety across the codebase
- HeroUI Native for primitive components
- Uniwind (Tailwind v4 for React Native) for styling
- Reanimated 4.2 for animations and hero transitions
- 37 custom themes with color palettes and background images
- Zustand for modern state management (migrating from Redux)
- Redux with redux-persist (legacy, being phased out)
- AsyncStorage for persisted state
- expo-secure-store for sensitive data (mnemonics, keys)
Cashu (Ecash)
- NUT-00 through NUT-13
- NUT-17 (WebSockets)
- NUT-18 (Payment requests)
- NUT-23 (Backup/restore)
Nostr
- NIP-04 (Encrypted DMs)
- NIP-05 (DNS verification)
- NIP-06 (Key derivation)
- NIP-17, NIP-44, NIP-59 (Gift-wrapped DMs)
- NIP-19 (Bech32 encoding)
Bitcoin
- Lightning Network (BOLT11)
- BIP-39 (Mnemonic seed phrases)
- BIP-32 (HD wallets)
Other
- NFC (NDEF Type 4 Tag protocol)
- UR codes (multi-frame QR)
- Lightning addresses
- LNURL-pay
Code Organization
The codebase follows a clear directory structure optimized for scalability:Principle:
app/ contains thin orchestration screens. Business logic lives in hooks/, helper/, and stores/. UI primitives in components/ui/, product features in components/blocks/.Provider Hierarchy
app/_layout.tsx:1-334 The app initializes through a carefully ordered provider hierarchy:Outer Providers (Stable)
These providers never remount, even across profile switches:- KeyboardProvider - Keyboard controller setup
- InitializationProvider - Splash screen & initialization state
- PersistGate - Redux rehydration (legacy)
- Provider (Redux) - Redux store (legacy)
- ThemeProvider - Theme context & dynamic colors
- HeroUINativeProvider - HeroUI component library
- HeroTransitionProvider - Shared element transitions
Inner Providers (Account-Scoped)
These providers remount when switching profiles (via Reactkey change):
- MigrationGate - Database migrations & version checks
- NostrKeysProvider - BIP-39 seed → Nostr key derivation (NIP-06)
- NostrNDKProvider - NDK initialization with SQLite cache
- CocoProvider - Coco-Cashu manager initialization
- ActionSheetProvider - Bottom sheet UI framework
- SheetProvider - Sheet registry
- PricelistProvider - Fiat price data (USD/EUR/GBP)
- PasscodeGate - 4-digit PIN protection
- AppGate - Terms acceptance & onboarding
Profile Management
Sovran supports multiple user profiles (wallets) derived from a single BIP-39 mnemonic:- Account 0 uses
coco.dbandnostr(backward compatible) - Account N uses
coco-N.dbandnostr-Nfor database isolation - Each profile has its own:
- Nostr identity (NIP-06 derivation path:
m/44'/1237'/0'/0/{accountIndex}) - Cashu wallet seed (BIP-32 path:
m/44'/129372'/0'/{accountIndex}'/0/0) - SQLite database for proofs, quotes, and history
- Zustand store state (persisted to profile-scoped AsyncStorage keys)
- Nostr identity (NIP-06 derivation path:
- Close drawer
- Show loading screen
- Cleanup Coco manager & disable watchers
- Switch
activeAccountIndexin profileStore - Rehydrate all profile-scoped Zustand stores
- React
keychange in_layout.tsxtriggers full provider remount
Key Management
All cryptographic material derives from a single BIP-39 mnemonic stored in expo-secure-store: providers/NostrKeysProvider.tsx:244-378 Derivation paths:| Purpose | Standard | Path | Output |
|---|---|---|---|
| Nostr identity | NIP-06 | m/44'/1237'/0'/0/{account} | nsec/npub |
| Cashu wallet | BIP-32 (NUT-13) | m/44'/129372'/0'/{account}'/0/0 | 12-word mnemonic |
- Derived keys are cached in expo-secure-store to avoid expensive re-derivation
- Cache is validated via mnemonic hash - invalidated on mnemonic change
- Fast path: less than 5ms to load from cache
- Slow path: ~200ms for full NIP-06 + BIP-32 derivation
Data Persistence
Sovran uses multiple storage layers:| Layer | Technology | Scope | Use Case |
|---|---|---|---|
| Secure Storage | expo-secure-store (encrypted) | Global | Mnemonics, derived keys, passcodes |
| Cashu Proofs | expo-sqlite via coco-cashu-expo-sqlite | Per-profile | Ecash proofs, mint quotes, keyset data |
| Nostr Events | expo-sqlite via NDK | Per-profile | Cached Nostr events, profiles, messages |
| App State | AsyncStorage via Zustand | Global or per-profile | Settings, balances, scan history |
| Redux | AsyncStorage via redux-persist | Global | Legacy state (being migrated out) |
Profile-scoped stores use AsyncStorage keys like
mint-store-profile-0, mint-store-profile-1, etc. Global stores use simple keys like settings-store.Initialization Flow
The app follows a staged initialization process to minimize perceived load time: Blocking stages (prevent app from showing):- Font loading
- Redux rehydration
- Database migrations
- Nostr key derivation
- Coco Manager instantiation
- Mint quote watcher
- Proof state watcher
- NDK initialization with relay connections
- NPC plugin sync (npubx.cash Lightning addresses)
Next Steps
Routing
Learn about Expo Router file-based navigation structure
State Management
Zustand stores, Redux migration, and profile-scoped state
Cashu Integration
Coco-Cashu manager, proofs, quotes, and mint operations
Nostr Integration
NDK, gift-wrapped DMs, and social graph integration