This document provides a comprehensive technical guide to the Mostro Mobile app initialization process, detailing how the application bootstraps its core systems, establishes Nostr connectivity, and prepares for trading operations.
Purpose: Understanding the complete initialization flow enables developers to maintain, extend, and troubleshoot the app's startup sequence effectively.
Scope: Covers the entire startup process from app launch to ready-for-trading state, including system dependencies, architecture patterns, and integration points.
The Mostro Mobile app follows a carefully orchestrated initialization sequence managed by appInitializerProvider. This sequence ensures all systems are properly configured and connected before user interaction begins.
// lib/shared/providers/app_init_provider.dart
final appInitializerProvider = FutureProvider<void>((ref) async {
// 1. Initialize NostrService
final nostrService = ref.read(nostrServiceProvider);
await nostrService.init(ref.read(settingsProvider));
// 2. Initialize KeyManager
final keyManager = ref.read(keyManagerProvider);
await keyManager.init();
// 3. Initialize SessionNotifier - Load active trading sessions
final sessionManager = ref.read(sessionNotifierProvider.notifier);
await sessionManager.init();
// 4. Create SubscriptionManager - Setup event subscriptions based on sessions
ref.read(subscriptionManagerProvider);
// 5. Configure background services for notifications and sync
ref.listen<Settings>(settingsProvider, (previous, next) {
ref.read(backgroundServiceProvider).updateSettings(next);
});
// 6. Initialize order notifiers for existing sessions
final cutoff = DateTime.now().subtract(const Duration(hours: Config.sessionExpirationHours));
for (final session in sessionManager.sessions) {
if(session.orderId == null || session.startTime.isBefore(cutoff)) continue;
ref.read(orderNotifierProvider(session.orderId!).notifier);
if (session.peer != null) {
ref.read(chatRoomsProvider(session.orderId!).notifier).subscribe();
}
}
});
The app implements a sophisticated first-run detection system that determines whether to show the walkthrough or proceed directly to the main app.
// lib/features/walkthrough/providers/first_run_provider.dart:23-28
Future<bool> _checkIfFirstRun() async {
final firstRunComplete = await _sharedPreferences.getBool(
SharedPreferencesKeys.firstRunComplete.value,
);
return firstRunComplete != true; // Returns true if first run
}
// lib/core/app_routes.dart:44-59
return firstRunState.when(
data: (isFirstRun) {
if (isFirstRun && state.matchedLocation != '/walkthrough') {
return '/walkthrough'; // Redirect to walkthrough
}
return null; // Proceed to requested route
},
loading: () {
return state.matchedLocation == '/walkthrough' ? null : '/walkthrough';
},
error: (_, __) => null,
);
- App Launch: User opens app for the first time
- First-Run Detection:
firstRunProviderdetects nofirstRunCompleteflag - Walkthrough Display: App redirects to
/walkthroughroute - Key Generation: During
appInitializerProvider,KeyManager.init()creates new mnemonic - Walkthrough Completion: User completes or skips walkthrough
- Flag Setting:
markFirstRunComplete()setsfirstRunComplete = true - Navigation: App redirects to home screen (
/)
// lib/features/walkthrough/screens/walkthrough_screen.dart:167-172
Future<void> _onIntroEnd(BuildContext context) async {
await ref.read(firstRunProvider.notifier).markFirstRunComplete();
if (context.mounted) {
context.go('/'); // Navigate to home
}
}
- App Launch: User opens app (not first time)
- First-Run Detection:
firstRunProviderdetectsfirstRunComplete = true - Direct Navigation: App proceeds directly to requested route (usually
/) - Key Loading: During
appInitializerProvider,KeyManager.init()loads existing keys - Session Restoration: Active trading sessions are restored from storage
- Ready State: App is immediately ready for trading operations
- First-time users: Mnemonic is generated during
KeyManager.init()inappInitializerProvider - Timing: Happens before the walkthrough is shown to the user
- Storage: Immediately stored in Flutter Secure Storage
- User awareness: User is not explicitly shown the mnemonic during first run
- User can view: Through Settings → Key Management screen
- User can regenerate: Through Key Management → Generate New Key
- User can import: Through Key Management → Import Key
- Backup responsibility: User must manually backup their mnemonic
- Automatic generation: No user input required for key generation
- Secure storage: Uses Flutter Secure Storage (encrypted at rest)
- No network transmission: Mnemonic never leaves the device
- Immediate availability: Keys ready for use without additional setup
- Mnemonic backup: User's responsibility to backup 12/24 word phrase
- Import capability: Users can restore from existing mnemonic
- Reset functionality: Users can generate new keys (clears all data)
Purpose: Establishes WebSocket connections to Nostr relays.
Process:
// lib/services/nostr_service.dart:35
await nostrService.init(settings);
What Happens:
- Reads relay configuration from settings
- Establishes WebSocket connections to configured relays
- Sets up connection pool for Nostr protocol communication
- Validates relay connectivity
Dependencies: Settings (relay configuration) Duration: ~200ms (network dependent)
Purpose: Initializes cryptographic key management system and handles mnemonic seed creation for first-time users.
Process:
// lib/features/key_manager/key_manager_provider.dart
await keyManager.init();
What Happens:
// lib/features/key_manager/key_manager.dart:16-23
Future<void> init() async {
if (!await hasMasterKey()) {
await generateAndStoreMasterKey(); // Creates new mnemonic seed
} else {
masterKeyPair = await _getMasterKey(); // Loads existing keys
tradeKeyIndex = await getCurrentKeyIndex();
}
}
For First-Time Users:
- Check for existing master key:
hasMasterKey()returnsfalse - Generate new mnemonic: Uses BIP-39 to create 12/24 word seed phrase
- Derive master key: Converts mnemonic to BIP-32 extended private key
- Store securely: Saves both mnemonic and master key in Flutter Secure Storage
- Initialize key index: Sets current trade key index to 1
- Create key pair: Generates NostrKeyPairs for immediate use
// lib/features/key_manager/key_manager.dart:31-46
Future<void> generateAndStoreMasterKey() async {
final mnemonic = _derivator.generateMnemonic(); // BIP-39 generation
await generateAndStoreMasterKeyFromMnemonic(mnemonic);
}
Future<void> generateAndStoreMasterKeyFromMnemonic(String mnemonic) async {
final masterKeyHex = _derivator.extendedKeyFromMnemonic(mnemonic);
await _storage.clear(); // Clear any existing data
await _storage.storeMnemonic(mnemonic); // Store in secure storage
await _storage.storeMasterKey(masterKeyHex); // Store derived master key
await setCurrentKeyIndex(1); // Initialize trade key index
masterKeyPair = await _getMasterKey(); // Create NostrKeyPairs
tradeKeyIndex = await getCurrentKeyIndex();
}
For Returning Users:
- Check for existing master key:
hasMasterKey()returnstrue - Load master key: Retrieves stored master key from secure storage
- Load key index: Gets current trade key index from SharedPreferences
- Create key pair: Generates NostrKeyPairs from stored master key
- Ready for trading: System is immediately ready for trade operations
// lib/features/key_manager/key_manager.dart:54-61
Future<NostrKeyPairs> _getMasterKey() async {
final masterKeyHex = await _storage.readMasterKey();
if (masterKeyHex == null) {
throw MasterKeyNotFoundException('No master key found in secure storage');
}
final privKey = _derivator.derivePrivateKey(masterKeyHex, 0);
return NostrKeyPairs(private: privKey);
}
- Mnemonic: Stored in Flutter Secure Storage (encrypted at rest)
- Master Key: Extended private key stored in Flutter Secure Storage
- Key Index: Current trade key index stored in SharedPreferences
- Trade Keys: Derived on-demand, never persisted
Dependencies: Secure storage access, BIP-39/BIP-32 libraries Duration: ~50ms (first-time), ~20ms (returning users)
Purpose: Loads all active trading sessions from local storage.
Process:
// lib/shared/notifiers/session_notifier.dart:32
await sessionManager.init();
What Happens:
Future<void> init() async {
final allSessions = await _storage.getAllSessions();
final cutoff = DateTime.now()
.subtract(const Duration(hours: Config.sessionExpirationHours));
for (final session in allSessions) {
if (session.startTime.isAfter(cutoff)) {
_sessions[session.orderId!] = session;
} else {
await _storage.deleteSession(session.orderId!);
_sessions.remove(session.orderId!);
}
}
state = sessions; // This triggers listeners
_scheduleCleanup();
}
Critical Aspects:
- Loads sessions from Sembast database
- Filters expired sessions (older than 72 hours)
- Updates
statewhich triggers all listeners - Must complete before SubscriptionManager setup
Dependencies: Session storage (Sembast database) Duration: ~100ms (database I/O dependent)
Purpose: Manages Nostr event subscriptions based on active sessions.
Process:
// lib/features/subscriptions/subscription_manager.dart:32
SubscriptionManager(this.ref) {
_initSessionListener();
_initializeExistingSessions(); // CRITICAL: DO NOT REMOVE - Prevents stuck orders bug
}
void _initSessionListener() {
_sessionListener = ref.listen<List<Session>>(
sessionNotifierProvider,
(previous, current) {
_updateAllSubscriptions(current);
},
fireImmediately: false, // Wait for proper initialization
onError: (error, stackTrace) {
_logger.e('Error in session listener', error: error, stackTrace: stackTrace);
},
);
}
The SubscriptionManager includes manual initialization to prevent orders from getting stuck in previous states after app restart:
void _initializeExistingSessions() {
try {
final existingSessions = ref.read(sessionNotifierProvider);
if (existingSessions.isNotEmpty) {
_logger.i('Initializing subscriptions for ${existingSessions.length} existing sessions');
_updateAllSubscriptions(existingSessions);
}
} catch (e, stackTrace) {
_logger.e('Error initializing existing sessions', error: e, stackTrace: stackTrace);
}
}
Why This Implementation is Required:
fireImmediately: falseprevents automatic subscription creation for existing sessions on app startup- Without this implementation, orders remain stuck in previous states (like
waiting-buyer-invoice) when they should show current states (likewaiting-payment) after app restart - The implementation creates subscriptions for existing sessions manually during constructor execution
- Protected by regression test:
test/features/subscriptions/subscription_manager_initialization_test.dart
Why fireImmediately: false is Used:
- Prevents the listener from executing before SessionNotifier.init() completes
- Ensures subscriptions are created with valid session data
- Avoids creating subscriptions with empty session lists
- Maintains proper initialization order dependencies
Historical Context:
- Commit 63dc124e set
fireImmediately: falseto fix relay switching issues where changing between Mostro instances would lose orders - This created a new bug where existing sessions wouldn't get subscriptions on app restart, causing orders to appear stuck in old states
- Manual initialization pattern was implemented to solve both problems: preserve relay switching fix while ensuring existing sessions get proper subscriptions
How This Works:
Initialization Sequence with Manual Implementation:
1. SubscriptionManager constructor called
2. _initSessionListener() runs → Listener registered with fireImmediately: false
3. _initializeExistingSessions() runs → Creates subscriptions for loaded sessions immediately
4. SessionNotifier.init() runs and loads sessions (already handled by step 3)
5. Future session changes trigger listener normally
6. Both existing and new sessions handled correctly
7. UI shows orders in correct states after app restart
Dual-Path Architecture:
- Listener Path: Handles future session changes (new trades, session deletions)
- Manual Path: Handles existing sessions during app initialization
- Result: Complete coverage of all session lifecycle scenarios
Dependencies: SessionNotifier state
Duration: ~10ms (manual initialization) + instantaneous (listener registration)
Purpose: Configures notification and background processing services.
Process:
ref.listen<Settings>(settingsProvider, (previous, next) {
ref.read(backgroundServiceProvider).updateSettings(next);
});
What Happens:
- Sets up listener for settings changes
- Configures notification delivery
- Initializes background sync processes
Dependencies: Settings provider Duration: ~10ms
Purpose: Creates individual order management notifiers for each active session.
Process:
for (final session in sessionManager.sessions) {
if(session.orderId == null || session.startTime.isBefore(cutoff)) continue;
// Create order notifier for this session
ref.read(orderNotifierProvider(session.orderId!).notifier);
// Initialize chat if peer exists
if (session.peer != null) {
ref.read(chatRoomsProvider(session.orderId!).notifier).subscribe();
}
}
What Happens:
- Iterates through all loaded sessions
- Creates
OrderNotifierfor each active order - Sets up chat room subscriptions for orders with assigned peers
- Establishes timeout detection and reversal systems
Dependencies: SessionNotifier state, ChatRoomProvider Duration: ~50ms per session
The initialization sequence has critical timing dependencies that must be respected:
- SessionNotifier must complete before SubscriptionManager: Event subscriptions require loaded session data to create proper filters
- NostrService must initialize first: Other components depend on established relay connectivity
- KeyManager initializes early: Required for session restoration and cryptographic operations
// Correct pattern for dependent initialization
_sessionListener = ref.listen<List<Session>>(
sessionNotifierProvider,
(previous, current) => _updateSubscriptions(current),
fireImmediately: false, // Wait for proper initialization
);
Why this pattern matters: Using fireImmediately: true would cause the listener to execute immediately with potentially empty session data, before SessionNotifier.init() completes. This would result in subscriptions being created with incorrect filters, causing UI inconsistencies like missing orders in "My Trades" screen.
// Proper async sequence in appInitializerProvider
await nostrService.init(settings); // Must complete first
await keyManager.init(); // Can run after NostrService
await sessionManager.init(); // Requires KeyManager
ref.read(subscriptionManagerProvider); // Requires SessionNotifier
The SubscriptionManager follows a standardized pattern for creating event subscriptions:
void _updateAllSubscriptions(List<Session> sessions) {
if (sessions.isEmpty) {
_clearAllSubscriptions();
return;
}
for (final type in SubscriptionType.values) {
_updateSubscription(type, sessions);
}
}
This pattern ensures subscriptions are only created when valid session data is available.
NostrFilter? _createFilterForType(SubscriptionType type, List<Session> sessions) {
switch (type) {
case SubscriptionType.orders:
return NostrFilter(
kinds: [1059], // Private gift-wrapped messages for active trading sessions
p: sessions.map((s) => s.tradeKey.public).toList(), // Messages to my trade keys
);
case SubscriptionType.chat:
return NostrFilter(
kinds: [1059], // Private gift-wrapped chat messages
p: sessions
.where((s) => s.sharedKey?.public != null)
.map((s) => s.sharedKey!.public)
.toList(), // Messages to my shared keys
);
case SubscriptionType.relayList:
return null; // Handled separately via subscribeToMostroRelayList()
}
}
The Mostro Mobile app uses a dual-channel architecture with two completely separate subscription systems, each handling different types of Nostr events for different purposes:
- Private Channel (SubscriptionManager): Handles encrypted user sessions
- Public Channel (OpenOrdersRepository + OrderNotifier): Handles public order discovery
Purpose: Manages private encrypted communications for active trading sessions.
Events Handled:
// lib/features/subscriptions/subscription_manager.dart
case SubscriptionType.orders:
return NostrFilter(
kinds: [1059], // Private gift-wrapped messages
p: sessions.map((s) => s.tradeKey.public).toList(), // To user's trade keys
);
case SubscriptionType.chat:
return NostrFilter(
kinds: [1059], // Private gift-wrapped chat messages
p: sessions
.where((s) => s.sharedKey?.public != null)
.map((s) => s.sharedKey!.public)
.toList(), // To user's shared keys
);
// Relay synchronization
case SubscriptionType.relayList:
return NostrFilter(
kinds: [10002], // Relay list events
authors: [mostroPublicKey], // From Mostro instance
);
Responsibilities:
- ✅ My Trades screen data
- ✅ Private chat messages with trading partners
- ✅ Session state management and updates
- ✅ Relay synchronization from Mostro instances
- ✅ Trade notifications and status changes
Data Flow:
Trading Partner → Kind 1059 (encrypted) → SubscriptionManager → MostroService → UI Updates
Purpose: Handles public order discovery and timeout detection.
Events Handled:
// lib/data/repositories/open_orders_repository.dart
final filter = NostrFilter(
kinds: [38383], // Public Mostro order events
since: filterTime, // Last 48 hours
authors: [_settings.mostroPublicKey], // Only from Mostro instance
);
// lib/features/order/notifiers/order_notifier.dart
// Uses 38383 events for timeout detection by comparing public state vs local state
Responsibilities:
- ✅ Order Book (home screen) - all available orders
- ✅ Order discovery - finding orders to take
- ✅ Timeout detection - comparing public events vs local session state
- ✅ Cancellation detection - detecting when orders are canceled
- ✅ Market data - public order information
Data Flow:
Mostro → Kind 38383 (public) → OpenOrdersRepository → Order Book UI
Mostro → Kind 38383 (public) → OrderNotifier → Timeout Detection
The real architectural principle behind this separation is subscription lifecycle management, not privacy levels.
Principle: Subscriptions that change based on user context
// These subscriptions RECONFIGURE when context changes
Kind 1059 (Orders): p: [myActiveTradeKeys] // Updates with active sessions
Kind 10002 (RelayList): authors: [currentMostro] // Updates with Mostro instance
Characteristics:
- ✅ Context-dependent: Change when user sessions or settings change
- ✅ Dynamic reconfiguration: Uses
_updateAllSubscriptions()logic - ✅ Shared lifecycle: Both require same subscribe/unsubscribe patterns
- ✅ State listeners: React to
sessionNotifierProviderandsettingsProvider
Why Kind 10002 is here: Relay lists need the same dynamic reconfiguration logic as session-based subscriptions, not because they're "private".
Principle: Subscriptions that remain constant during app lifecycle
// This subscription is CONSTANT throughout app session
Kind 38383: authors: [mostroInstance] // Always same filter, independent of user context
Characteristics:
- ✅ Context-independent: Not affected by user's trading sessions
- ✅ Static configuration: Initialize once at startup
- ✅ Global platform data: Market information for all users
- ✅ Simple lifecycle: No dynamic updates needed
| Aspect | SubscriptionManager (Dynamic) | OpenOrdersRepository (Static) |
|---|---|---|
| Update Trigger | Session/Settings changes | App startup only |
| Reconfiguration | Frequent, context-based | None after initialization |
| State Dependency | Depends on user context | Independent of user state |
| Complexity | High (dynamic management) | Low (simple subscription) |
| Purpose | "Events that change with user context" | "Global platform data" |
- State Management: Different update patterns require different architectures
- Performance: Static subscriptions avoid unnecessary reconnections
- Complexity Isolation: Dynamic logic separated from simple global subscriptions
- Maintainability: Clear separation of concerns by update frequency
- Shared Logic Reuse: Kind 1059 and 10002 share the same reconfiguration system
- Security: Private events encrypted, public events accessible
- Performance: Focused subscriptions reduce network overhead
- Scalability: Independent scaling of personal vs market data
- Fault Tolerance: Failure isolation between systems
// NEVER in SubscriptionManager - it only handles private events
❌ kinds: [38383] // This would be wrong - public events not handled here
✅ kinds: [1059] // Correct - only private encrypted events
// OpenOrdersRepository handles all 38383 events
✅ kinds: [38383] // Public order announcements from Mostro
✅ authors: [mostroPublicKey] // Only from configured Mostro instance
✅ since: filterTime // Recent orders only
// Private events: Filter by recipient (who can decrypt)
p: [myTradeKeys] // Only messages I can decrypt
// Public events: Filter by author and time
authors: [mostroPublicKey] // Only from Mostro
since: filterTime // Recent orders only
Purpose: Processes private encrypted messages (Kind 1059) from active trading sessions.
Integration:
// lib/services/mostro_service.dart:27
_ordersSubscription = ref.read(subscriptionManagerProvider).orders.listen(
_onData,
onError: (error, stackTrace) {
_logger.e('Error in orders subscription', error: error, stackTrace: stackTrace);
},
cancelOnError: false,
);
What MostroService Actually Processes:
Future<void> _onData(NostrEvent event) async {
// 1. Event deduplication
if (await eventStore.hasItem(event.id!)) return;
// 2. Find matching session by trade key
final matchingSession = sessions.firstWhereOrNull(
(s) => s.tradeKey.public == event.recipient,
);
// 3. Decrypt NIP-59 gift-wrapped message
final decryptedEvent = await event.unWrap(privateKey);
// 4. Parse Mostro protocol message
final msg = MostroMessage.fromJson(result[0]);
// 5. Store in local database
await messageStorage.addMessage(decryptedEvent.id!, msg);
}
Integration Result: ✅ Proper Event Handling - MostroService receives private encrypted events (Kind 1059) from properly initialized subscriptions, NOT public events (Kind 38383).
Purpose: Automatically syncs relay lists from Mostro instances.
Integration:
// lib/features/relays/relays_notifier.dart:488
_subscriptionManager?.subscribeToMostroRelayList(mostroPubkey);
Integration Result: ✅ Independent Operation - Relay sync uses separate subscription methods and operates independently.
Purpose: Handle public order discovery and timeout detection using Kind 38383 events.
// lib/data/repositories/open_orders_repository.dart
final filter = NostrFilter(
kinds: [38383], // Public Mostro order events
since: filterTime, // Last 48 hours
authors: [_settings.mostroPublicKey], // Only from Mostro instance
);
Integration:
// lib/shared/providers/order_repository_provider.dart
final orderEventsProvider = StreamProvider<List<NostrEvent>>((ref) {
final orderRepository = ref.read(orderRepositoryProvider);
return orderRepository.eventsStream; // Streams public 38383 events
});
Powers: Order Book (home screen), market discovery, order taking
// lib/features/order/notifiers/order_notifier.dart
// Compares public 38383 events vs local session state to detect:
// - Order timeouts (waitingPayment → pending)
// - Order cancellations (active → canceled)
// - State synchronization between public announcements and private sessions
Integration Result: ✅ Independent Operation - Public order systems operate independently of SubscriptionManager initialization.
Purpose: Handles encrypted peer-to-peer messaging via Kind 1059 events.
Integration: Uses SubscriptionManager chat stream for private messages between trading partners.
Integration Result: ✅ Proper Initialization - Chat messages are properly initialized with session data.
Testing the complete app initialization from a clean state:
# Clean app state and dependencies
flutter clean
flutter pub get
dart run build_runner build -d
# Test cold start
flutter run --release
Key verification points:
- All components initialize without errors
- UI is fully responsive after initialization
- No race conditions or timing issues occur
Verifying proper component initialization order:
// Monitor initialization sequence in logs
NostrService → KeyManager → SessionNotifier → SubscriptionManager → Background Services
Log patterns to verify:
NostrService initialized successfully with X relaysKeyManager: Master keys loaded from secure storageSessionNotifier: Loaded X sessions from storageSubscription created for SubscriptionType.orders with X sessions
Verify that systems integrate correctly:
- Session Restoration: Active sessions load properly after app restart
- Subscription Setup: Event subscriptions match loaded sessions
- Background Services: Notifications and sync services activate correctly
- Relay Connectivity: All configured relays establish connections
Current Implementation Performance:
- Single subscription creation: ~10-50ms (network dependent)
- No recreation needed: Eliminates secondary overhead
- Total: ~10-50ms + no UI flickering
Architectural Benefits:
- ✅ Eliminates UI flickering during initialization
- ✅ Reduces initialization complexity
- ✅ Prevents race conditions between components
- ✅ Maintains proper dependency order
The exact performance characteristics vary by environment, but the architectural approach ensures consistent behavior across different conditions.
When adding new components to the app initialization sequence:
- Identify Dependencies: Determine which existing systems your component requires
- Placement in Sequence: Add initialization calls in the correct order within
appInitializerProvider - Async Patterns: Use
awaitfor components that other systems depend on - Error Handling: Implement proper error handling and recovery mechanisms
// Example: Adding a new component
final appInitializerProvider = FutureProvider<void>((ref) async {
// ... existing initialization ...
// Add new component after its dependencies
final newComponent = ref.read(newComponentProvider);
await newComponent.init(); // If other systems depend on this
// Or without await if independent
ref.read(independentComponentProvider);
});
- Listen Pattern: Use
fireImmediately: falsewhen depending on other providers - Explicit Dependencies: Clearly document what each provider requires
- State Validation: Check that dependencies are initialized before using them
- Error Propagation: Handle dependency initialization failures gracefully
- Monitor initialization time as components are added
- Consider parallel initialization for independent systems
- Implement lazy loading for non-critical components
- Use dependency injection patterns to manage complexity
- Profile initialization performance regularly
- Identify bottlenecks in the startup sequence
- Consider background initialization for heavy operations
- Implement progressive enhancement patterns
- Maintain comprehensive logging throughout initialization
- Add performance metrics for each initialization phase
- Implement health checks for critical components
- Use tracing to understand initialization flow in production
The Mostro Mobile app initialization process represents a sophisticated bootstrap sequence that ensures all critical systems are properly configured before user interaction begins. This comprehensive initialization flow demonstrates several key architectural principles:
- Sequential Initialization: Critical components initialize in dependency order
- Proper Timing:
fireImmediately: falsepatterns prevent race conditions - Error Handling: Graceful failure handling at each initialization stage
- Dynamic Subscriptions: SubscriptionManager handles context-dependent events (Kind 1059, 10002)
- Static Subscriptions: OpenOrdersRepository manages global platform data (Kind 38383)
- Separation of Concerns: Clear boundaries between private trading data and public market information
- Clear Patterns: Well-defined approaches for adding new components
- Provider Integration: Consistent use of Riverpod for dependency injection
- Scalable Structure: Architecture supports growth without major refactoring
- Initialization Order Matters: Carefully consider dependencies when adding new components
- Timing Patterns: Use appropriate
fireImmediatelysettings based on dependency requirements - Architecture Separation: Maintain clear boundaries between different data channels and responsibilities
- Documentation Value: Technical documentation must accurately reflect implementation details
This initialization system provides a robust foundation for the app's trading operations while maintaining clear architectural boundaries and extensible patterns for future development.
Last Updated: September 17, 2025