diff --git a/mobile-app/android/app/build.gradle b/mobile-app/android/app/build.gradle index c58ea49a..94e98a67 100644 --- a/mobile-app/android/app/build.gradle +++ b/mobile-app/android/app/build.gradle @@ -1,5 +1,8 @@ plugins { id "com.android.application" + // START: FlutterFire Configuration + id 'com.google.gms.google-services' + // END: FlutterFire Configuration id "kotlin-android" // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id "dev.flutter.flutter-gradle-plugin" diff --git a/mobile-app/android/settings.gradle b/mobile-app/android/settings.gradle index 530045a2..044babb5 100644 --- a/mobile-app/android/settings.gradle +++ b/mobile-app/android/settings.gradle @@ -19,6 +19,9 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version '8.13.0' apply false + // START: FlutterFire Configuration + id "com.google.gms.google-services" version "4.3.15" apply false + // END: FlutterFire Configuration id "org.jetbrains.kotlin.android" version "2.2.0" apply false } diff --git a/mobile-app/firebase.json b/mobile-app/firebase.json new file mode 100644 index 00000000..160015e6 --- /dev/null +++ b/mobile-app/firebase.json @@ -0,0 +1,30 @@ +{ + "flutter": { + "platforms": { + "android": { + "default": { + "projectId": "quantus-wallet", + "appId": "1:700047185713:android:151f32080a837021d98210", + "fileOutput": "android/app/google-services.json" + } + }, + "ios": { + "default": { + "projectId": "quantus-wallet", + "appId": "1:700047185713:ios:4689e532e8a4f174d98210", + "uploadDebugSymbols": false, + "fileOutput": "ios/Runner/GoogleService-Info.plist" + } + }, + "dart": { + "lib/firebase_options.dart": { + "projectId": "quantus-wallet", + "configurations": { + "android": "1:700047185713:android:151f32080a837021d98210", + "ios": "1:700047185713:ios:4689e532e8a4f174d98210" + } + } + } + } + } +} diff --git a/mobile-app/ios/Runner.xcodeproj/project.pbxproj b/mobile-app/ios/Runner.xcodeproj/project.pbxproj index 03bf29ee..55defffc 100644 --- a/mobile-app/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile-app/ios/Runner.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; B271D7AD0A4C6686E7F60214 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFB02A1061CC8E987BB71514 /* Pods_RunnerTests.framework */; }; + C2FAA8FDFD5122ED16B47C48 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 44914B43E735DCED6C9EBA96 /* GoogleService-Info.plist */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -50,6 +51,7 @@ 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 44914B43E735DCED6C9EBA96 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; 4672F8772DB9DA61003B0FFF /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 617F42319F855E3DC9D75E46 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; @@ -129,6 +131,7 @@ 331C8082294A63A400263BE5 /* RunnerTests */, 697901FF368C5DA3A4CA46A0 /* Pods */, CEA34285B5A0D76CBF7D88A6 /* Frameworks */, + 44914B43E735DCED6C9EBA96 /* GoogleService-Info.plist */, ); sourceTree = ""; }; @@ -200,6 +203,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 2B5704DCBF3C1C1DEADD5FFB /* [CP] Embed Pods Frameworks */, + 6E98DD3DBFF3FA4BAA669C2E /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -265,6 +269,7 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + C2FAA8FDFD5122ED16B47C48 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -304,6 +309,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 6E98DD3DBFF3FA4BAA669C2E /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -478,6 +500,7 @@ DEVELOPMENT_TEAM = 8BRRAHLVW5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -673,6 +696,7 @@ DEVELOPMENT_TEAM = 8BRRAHLVW5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -705,6 +729,7 @@ DEVELOPMENT_TEAM = 8BRRAHLVW5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/mobile-app/ios/Runner/AppDelegate.swift b/mobile-app/ios/Runner/AppDelegate.swift index bc815ab5..36cafd20 100644 --- a/mobile-app/ios/Runner/AppDelegate.swift +++ b/mobile-app/ios/Runner/AppDelegate.swift @@ -1,6 +1,7 @@ import Flutter import UIKit import flutter_local_notifications +import FirebaseMessaging @main @objc class AppDelegate: FlutterAppDelegate { @@ -12,11 +13,27 @@ import flutter_local_notifications GeneratedPluginRegistrant.register(with: registry) } + // Set the notification center delegate for flutter_local_notifications + // foreground presentation. Must happen before Firebase configures. if #available(iOS 10.0, *) { - UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate } GeneratedPluginRegistrant.register(with: self) + + // Register for remote notifications (required when swizzling is disabled). + application.registerForRemoteNotifications() + return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + // With FirebaseAppDelegateProxyEnabled = NO, we must manually forward + // the APNs device token to Firebase Messaging. + override func application( + _ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data + ) { + Messaging.messaging().apnsToken = deviceToken + super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) + } } diff --git a/mobile-app/ios/Runner/GoogleService-Info.plist b/mobile-app/ios/Runner/GoogleService-Info.plist new file mode 100644 index 00000000..27fb595d --- /dev/null +++ b/mobile-app/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + AIzaSyBnz9E3PdYKjKmAd7hstSiiwMe9bxtlEnQ + GCM_SENDER_ID + 700047185713 + PLIST_VERSION + 1 + BUNDLE_ID + com.quantus.mobile-wallet + PROJECT_ID + quantus-wallet + STORAGE_BUCKET + quantus-wallet.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:700047185713:ios:4689e532e8a4f174d98210 + + \ No newline at end of file diff --git a/mobile-app/ios/Runner/Info.plist b/mobile-app/ios/Runner/Info.plist index f43a992f..2c5ed3ae 100644 --- a/mobile-app/ios/Runner/Info.plist +++ b/mobile-app/ios/Runner/Info.plist @@ -1,63 +1,70 @@ - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Quantus - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Quantus - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - ITSAppUsesNonExemptEncryption - - LSRequiresIPhoneOS - - LSApplicationQueriesSchemes - - https - - FlutterDeepLinkingEnabled - - NSCameraUsageDescription - We need camera access to scan QR codes for sending funds. - NSFaceIDUsageDescription - Use Face ID to authenticate and securely access your wallet. - UIApplicationSupportsIndirectInputEvents - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIStatusBarHidden - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Quantus + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Quantus + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + FlutterDeepLinkingEnabled + + ITSAppUsesNonExemptEncryption + + LSApplicationQueriesSchemes + + https + + LSRequiresIPhoneOS + + NSCameraUsageDescription + We need camera access to scan QR codes for sending funds. + NSFaceIDUsageDescription + Use Face ID to authenticate and securely access your wallet. + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + remote-notification + fetch + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + FirebaseAppDelegateProxyEnabled + + diff --git a/mobile-app/lib/app_initializer.dart b/mobile-app/lib/app_initializer.dart index efcd9695..9b99fa6f 100644 --- a/mobile-app/lib/app_initializer.dart +++ b/mobile-app/lib/app_initializer.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:resonance_network_wallet/services/firebase_messaging_service.dart'; import 'package:resonance_network_wallet/services/history_polling_manager.dart'; import 'package:resonance_network_wallet/services/local_notifications_service.dart'; @@ -26,6 +27,9 @@ class _AppInitializerState extends ConsumerState { try { final notificationService = ref.read(localNotificationsServiceProvider); await notificationService.init(); + + final fcmService = ref.read(firebaseMessagingServiceProvider); + await fcmService.init(); ref.read(historyPollingManagerProvider); } catch (e, stackTrace) { diff --git a/mobile-app/lib/features/main/screens/app.dart b/mobile-app/lib/features/main/screens/app.dart index 8ce59d32..ec6ed043 100644 --- a/mobile-app/lib/features/main/screens/app.dart +++ b/mobile-app/lib/features/main/screens/app.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:resonance_network_wallet/features/main/screens/authentication_wrapper.dart'; import 'package:resonance_network_wallet/features/main/screens/wallet_initializer.dart'; import 'package:resonance_network_wallet/v2/theme/app_theme.dart'; +import 'package:resonance_network_wallet/services/firebase_messaging_service.dart'; import 'package:resonance_network_wallet/services/local_notifications_service.dart'; import 'package:resonance_network_wallet/services/notification_integration_service.dart'; import 'package:resonance_network_wallet/services/referral_service.dart'; @@ -32,6 +33,7 @@ class _ResonanceWalletAppState extends ConsumerState { ref.read(deepLinkServiceProvider).init(navigatorKey); ref.read(localNotificationsServiceProvider).setupNotificationsClickListener(navigatorKey); ref.read(localNotificationsServiceProvider).handleLaunchByNotification(navigatorKey); + ref.read(firebaseMessagingServiceProvider).setupNotificationTapHandlers(navigatorKey); if (Platform.isAndroid) _referralService.checkPlayStoreReferralCode(); }); } diff --git a/mobile-app/lib/firebase_options.dart b/mobile-app/lib/firebase_options.dart new file mode 100644 index 00000000..bd421bda --- /dev/null +++ b/mobile-app/lib/firebase_options.dart @@ -0,0 +1,68 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: type=lint +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for web - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyCdxQJC8ONiwRX9S4iOz0cDoF-QzVig32Y', + appId: '1:700047185713:android:151f32080a837021d98210', + messagingSenderId: '700047185713', + projectId: 'quantus-wallet', + storageBucket: 'quantus-wallet.firebasestorage.app', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyBnz9E3PdYKjKmAd7hstSiiwMe9bxtlEnQ', + appId: '1:700047185713:ios:4689e532e8a4f174d98210', + messagingSenderId: '700047185713', + projectId: 'quantus-wallet', + storageBucket: 'quantus-wallet.firebasestorage.app', + iosBundleId: 'com.quantus.mobile-wallet', + ); +} diff --git a/mobile-app/lib/main.dart b/mobile-app/lib/main.dart index 5673b461..53c31a34 100644 --- a/mobile-app/lib/main.dart +++ b/mobile-app/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -8,6 +9,7 @@ import 'package:resonance_network_wallet/features/main/screens/app.dart'; import 'package:resonance_network_wallet/utils/env_utils.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:telemetrydecksdk/telemetrydecksdk.dart'; +import 'package:resonance_network_wallet/firebase_options.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -16,6 +18,9 @@ void main() async { // Initialize Supabase await Supabase.initialize(url: EnvUtils.supabaseUrl, anonKey: EnvUtils.supabaseKey); await QuantusSdk.init(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); Telemetrydecksdk.start( const TelemetryManagerConfiguration( appID: '098B4397-8426-4054-B379-0E4C53D2CA63', diff --git a/mobile-app/lib/providers/notification_provider.dart b/mobile-app/lib/providers/notification_provider.dart index 209505e5..e3080ebb 100644 --- a/mobile-app/lib/providers/notification_provider.dart +++ b/mobile-app/lib/providers/notification_provider.dart @@ -180,7 +180,9 @@ class NotificationNotifier extends StateNotifier> { _localNotificationsService.showOrScheduleNotification(notification); break; case NotificationSource.remote: - // To be handled in the future + // Remote notifications arriving via FCM foreground listener are + // shown as local push notifications by FirebaseMessagingService. + // No additional handling needed here. break; } } diff --git a/mobile-app/lib/services/firebase_messaging_service.dart b/mobile-app/lib/services/firebase_messaging_service.dart new file mode 100644 index 00000000..ad2ef500 --- /dev/null +++ b/mobile-app/lib/services/firebase_messaging_service.dart @@ -0,0 +1,186 @@ +import 'dart:io'; + +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/models/notification_models.dart'; +import 'package:resonance_network_wallet/providers/notification_provider.dart'; +import 'package:resonance_network_wallet/providers/route_intent_providers.dart'; +import 'package:resonance_network_wallet/services/local_notifications_service.dart'; +import 'package:resonance_network_wallet/services/transaction_service.dart'; + +/// Top-level handler for background/terminated FCM messages. +/// Must be a top-level function (not a class method) for Firebase. +@pragma('vm:entry-point') +Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { + // Background messages are automatically shown by the OS as notifications. + // No additional handling is needed here unless you want to persist data. + debugPrint('FCM background message: ${message.messageId}'); +} + +class FirebaseMessagingService { + final Ref _ref; + final FirebaseMessaging _messaging = FirebaseMessaging.instance; + final SenotiService _senotiService = SenotiService(); + + bool _isInitialized = false; + + FirebaseMessagingService(this._ref); + + /// Initialize FCM: request permissions, get token, and set up listeners. + Future init() async { + if (_isInitialized) return; + + await _requestPermission(); + await _getToken(); + + _setupForegroundMessageListener(); + _setupTokenRefreshListener(); + _setupBackgroundMessageListener(); + + _isInitialized = true; + } + + /// Request notification permissions (required for iOS, Android 13+). + Future _requestPermission() async { + final settings = await _messaging.requestPermission(alert: true, badge: true, sound: true, provisional: false); + + debugPrint('FCM permission status: ${settings.authorizationStatus}'); + + // On iOS, set foreground notification presentation options. + // This tells iOS to NOT show the system banner when the app is in the + // foreground, because we handle it ourselves via local notifications. + if (Platform.isIOS) { + await _messaging.setForegroundNotificationPresentationOptions(alert: false, badge: true, sound: false); + } + } + + /// Get the FCM device token (useful for server-side targeting). + Future _getToken() async { + final token = await _messaging.getToken(); + debugPrint('FCM token: $token'); + + if (token != null && token.isNotEmpty) { + await _senotiService.registerDevice(token, Platform.operatingSystem); + } + } + + /// Listen for token refresh events. + void _setupTokenRefreshListener() { + _messaging.onTokenRefresh.listen((newToken) { + debugPrint('FCM token refreshed: $newToken'); + + _senotiService.registerDevice(newToken, Platform.operatingSystem); + }); + } + + /// Listen for messages when the app is in the foreground. + /// FCM does NOT show a system notification in this case, so we convert + /// the message to a NotificationData and show it via local notifications. + void _setupForegroundMessageListener() { + FirebaseMessaging.onMessage.listen((RemoteMessage message) { + debugPrint('FCM foreground message: ${message.messageId}'); + + final notification = _remoteMessageToNotificationData(message); + if (notification == null) return; + + // Add to the notification provider (persists + sends to stream). + final notifier = _ref.read(notificationProvider.notifier); + notifier.addRemoteNotification(notification); + + // Show as a local push notification so the user sees a banner. + final localNotificationService = _ref.read(localNotificationsServiceProvider); + localNotificationService.showOrScheduleNotification(notification); + }); + } + + void _setupBackgroundMessageListener() { + FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); + } + + /// Handle the user tapping on an FCM notification that launched/resumed the app. + /// Call this after the navigator key is available. + void setupNotificationTapHandlers(GlobalKey navigatorKey) { + // Handle tap when app was in background (not terminated). + FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { + debugPrint('FCM notification tapped (background): ${message.messageId}'); + _handleNotificationTap(message, navigatorKey); + }); + + // Handle tap when app was terminated. + _handleInitialMessage(navigatorKey); + } + + /// Check if the app was launched from a terminated state by tapping an FCM notification. + Future _handleInitialMessage(GlobalKey navigatorKey) async { + final initialMessage = await _messaging.getInitialMessage(); + if (initialMessage != null) { + debugPrint('FCM initial message (terminated): ${initialMessage.messageId}'); + _handleNotificationTap(initialMessage, navigatorKey); + } + } + + /// Navigate based on the FCM message data payload. + void _handleNotificationTap(RemoteMessage message, GlobalKey navigatorKey) { + final data = message.data; + if (data.isEmpty) return; + + final txService = _ref.read(transactionServiceProvider); + final event = txService.deserializeTxEventFromJsonIfPossible(data); + + if (event != null) { + _ref.read(transactionIntentProvider.notifier).state = event; + navigatorKey.currentState?.pushNamed('/transactions'); + } + } + + /// Convert an FCM [RemoteMessage] into the app's [NotificationData] model. + NotificationData? _remoteMessageToNotificationData(RemoteMessage message) { + final notification = message.notification; + final data = message.data; + + final title = notification?.title ?? data['title'] as String? ?? 'Notification'; + final body = notification?.body ?? data['body'] as String? ?? ''; + + // Parse optional fields from the data payload. + final accountId = data['accountId'] as String? ?? ''; + final accountName = data['accountName'] as String? ?? ''; + final typeStr = data['type'] as String?; + final intentStr = data['intent'] as String?; + + final type = NotificationType.values.firstWhere((e) => e.name == typeStr, orElse: () => NotificationType.info); + + final intent = NotificationIntent.values.firstWhere( + (e) => e.name == intentStr, + orElse: () => NotificationIntent.others, + ); + + // Build metadata from the data payload (excluding fields we already extracted). + final metadata = Map.from(data) + ..remove('title') + ..remove('body') + ..remove('accountId') + ..remove('accountName') + ..remove('type') + ..remove('intent'); + + return NotificationData( + id: 'remote_${message.messageId ?? DateTime.now().millisecondsSinceEpoch}', + accountId: accountId, + type: type, + intent: intent, + source: NotificationSource.remote, + title: title, + message: body, + accountName: accountName, + timestamp: DateTime.now(), + persistent: true, + metadata: metadata.isNotEmpty ? metadata : null, + ); + } +} + +final firebaseMessagingServiceProvider = Provider((ref) { + return FirebaseMessagingService(ref); +}); diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index 66d8d4a3..b91711bb 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -58,6 +58,8 @@ dependencies: flutter_timezone: ^5.0.1 collection: ^1.19.1 glass_kit: ^4.0.2 + firebase_core: ^4.4.0 + firebase_messaging: ^16.1.1 dev_dependencies: flutter_test: diff --git a/quantus_sdk/lib/quantus_sdk.dart b/quantus_sdk/lib/quantus_sdk.dart index 3628c09e..e258c28a 100644 --- a/quantus_sdk/lib/quantus_sdk.dart +++ b/quantus_sdk/lib/quantus_sdk.dart @@ -59,6 +59,7 @@ export 'src/services/settings_service.dart'; export 'src/services/substrate_service.dart'; export 'src/services/swap_service.dart'; export 'src/services/taskmaster_service.dart'; +export 'src/services/senoti_service.dart'; export 'src/extensions/account_extension.dart'; export 'src/quantus_signing_payload.dart'; export 'src/quantus_payload_parser.dart'; diff --git a/quantus_sdk/lib/src/constants/app_constants.dart b/quantus_sdk/lib/src/constants/app_constants.dart index e3bf3165..131d070e 100644 --- a/quantus_sdk/lib/src/constants/app_constants.dart +++ b/quantus_sdk/lib/src/constants/app_constants.dart @@ -23,6 +23,8 @@ class AppConstants { // static const String taskMasterEndpoint = 'http://localhost:3000/api'; static const String taskMasterEndpoint = 'https://quests.quantus.com/api'; + static const String senotiEndpoint = 'http://localhost:3100/api'; + static const String explorerEndpoint = 'https://explorer.quantus.com'; static const String helpAndSupportUrl = 'https://t.me/c/quantusnetwork/2457'; static const String termsOfServiceUrl = 'https://www.quantus.com/terms-and-privacy'; diff --git a/quantus_sdk/lib/src/services/senoti_service.dart b/quantus_sdk/lib/src/services/senoti_service.dart new file mode 100644 index 00000000..992b1969 --- /dev/null +++ b/quantus_sdk/lib/src/services/senoti_service.dart @@ -0,0 +1,100 @@ +import 'dart:convert'; + +import 'package:convert/convert.dart' as convert_hex; +import 'package:http/http.dart' as http; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/src/rust/api/crypto.dart' as crypto; + +class SenotiAuthClient { + final String senotiEndpointUrl; + final http.Client _client; + + SenotiAuthClient(this.senotiEndpointUrl, {http.Client? client}) : _client = client ?? http.Client(); + + Future> requestChallenge() async { + print('request challenge'); + final r = await _client.get( + Uri.parse('$senotiEndpointUrl/auth/request-challenge'), + headers: {'content-type': 'application/json'}, + ); + if (r.statusCode != 200) { + throw Exception('request-challenge failed: ${r.statusCode} ${r.body}'); + } + final j = jsonDecode(r.body) as Map; + return {'temp_session_id': j['temp_session_id'] as String, 'challenge': j['challenge'] as String}; + } + + Future registerDevice({ + required String ss58Address, + required String publicKeyHex, + required Future Function(List messageBytes) signHex, + required String token, + required String platform, + }) async { + final ch = await requestChallenge(); + print('challenge: $ch'); + final msg = + 'device-registrar:device-registration:1|challenge=${ch['challenge']}|address=$ss58Address|platform=$platform|token=$token'; + print('msg: $msg'); + final sigHex = await signHex(utf8.encode(msg)); + + print('verify ${ch['temp_session_id']!} $senotiEndpointUrl'); + final r = await _client.post( + Uri.parse('$senotiEndpointUrl/devices'), + headers: {'content-type': 'application/json'}, + body: jsonEncode({ + 'temp_session_id': ch['temp_session_id']!, + 'address': ss58Address, + 'public_key': publicKeyHex, + 'signature': sigHex, + }), + ); + if (r.statusCode != 202) { + throw Exception('verify failed: ${r.statusCode}'); + } + print('verify response: ${r.body}'); + } +} + +// Senoti service singleton +class SenotiService { + static final SenotiService _instance = SenotiService._internal(); + factory SenotiService() => _instance; + SenotiService._internal(); + + final SettingsService _settingsService = SettingsService(); + final HdWalletService _hd = HdWalletService(); + + SenotiAuthClient get _client => SenotiAuthClient(AppConstants.senotiEndpoint); + + Future registerDevice(String token, String platform) async { + final mnemonic = await _settingsService.getMnemonic(0); + if (mnemonic == null) { + throw Exception('Mnemonic not found.'); + } + final keypair = _hd.keyPairAtIndex(mnemonic, 0); + final ss58Address = keypair.ss58Address; + final publicKeyHex = convert_hex.hex.encode(keypair.publicKey); + + Future signHex(List messageBytes) async { + final sig = crypto.signMessage(keypair: keypair, message: messageBytes); + return convert_hex.hex.encode(sig); + } + + await _client.registerDevice( + ss58Address: ss58Address, + publicKeyHex: publicKeyHex, + signHex: signHex, + token: token, + platform: platform, + ); + } + + Future getMainAccount() async { + final account = await _settingsService.getAccount(walletIndex: 0, index: 0); + if (account == null) { + throw Exception('No main account - this method should probably not be called when logged out'); + } + return account; + } +} diff --git a/quantus_sdk/pubspec.lock b/quantus_sdk/pubspec.lock index 334c9cbb..c77ae1d3 100644 --- a/quantus_sdk/pubspec.lock +++ b/quantus_sdk/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" url: "https://pub.dev" source: hosted - version: "88.0.0" + version: "93.0.0" adaptive_number: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: analyzer - sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f" + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b url: "https://pub.dev" source: hosted - version: "8.1.1" + version: "10.0.1" args: dependency: transitive description: @@ -65,14 +65,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "275bf6bb2a00a9852c28d4e0b410da1d833a734d57d39d44f94bfc895a484ec3" + url: "https://pub.dev" + source: hosted + version: "4.0.4" build_cli_annotations: dependency: transitive description: name: build_cli_annotations - sha256: b59d2769769efd6c9ff6d4c4cede0be115a566afc591705c2040b707534b1172 + sha256: e563c2e01de8974566a1998410d3f6f03521788160a02503b0b1f1a46c7b3d95 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" + url: "https://pub.dev" + source: hosted + version: "1.2.0" built_collection: dependency: transitive description: @@ -85,10 +101,10 @@ packages: dependency: transitive description: name: built_value - sha256: ba95c961bafcd8686d1cf63be864eb59447e795e124d98d6a27d91fcd13602fb + sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8" url: "https://pub.dev" source: hosted - version: "8.11.1" + version: "8.12.3" characters: dependency: transitive description: @@ -97,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" clock: dependency: transitive description: @@ -109,10 +133,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" url: "https://pub.dev" source: hosted - version: "4.10.1" + version: "4.11.1" collection: dependency: "direct main" description: @@ -145,46 +169,38 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" - cross_file: - dependency: transitive - description: - name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" - url: "https://pub.dev" - source: hosted - version: "0.3.4+2" crypto: dependency: "direct main" description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" cryptography: dependency: transitive description: name: cryptography - sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05 + sha256: "3eda3029d34ec9095a27a198ac9785630fe525c0eb6a49f3d575272f8e792ef0" url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.9.0" dart_style: dependency: transitive description: name: dart_style - sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697 + sha256: "15a7db352c8fc6a4d2bc475ba901c25b39fe7157541da4c16eacce6f8be83e49" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.5" dbus: dependency: transitive description: name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.12" decimal: dependency: "direct main" description: @@ -213,10 +229,10 @@ packages: dependency: transitive description: name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.8" fake_async: dependency: transitive description: @@ -229,10 +245,10 @@ packages: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" file: dependency: transitive description: @@ -295,50 +311,50 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40 url: "https://pub.dev" source: hosted - version: "9.2.4" - flutter_secure_storage_linux: + version: "10.0.0" + flutter_secure_storage_darwin: dependency: transitive description: - name: flutter_secure_storage_linux - sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + name: flutter_secure_storage_darwin + sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3" url: "https://pub.dev" source: hosted - version: "1.2.3" - flutter_secure_storage_macos: + version: "0.2.0" + flutter_secure_storage_linux: dependency: transitive description: - name: flutter_secure_storage_macos - sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + name: flutter_secure_storage_linux + sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.0.0" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.0.1" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.0" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.1.0" flutter_test: dependency: "direct dev" description: flutter @@ -374,10 +390,10 @@ packages: dependency: "direct main" description: name: http - sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.6.0" http_parser: dependency: transitive description: @@ -408,30 +424,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.2" - js: + json_annotation: dependency: transitive description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + name: json_annotation + sha256: "805fa86df56383000f640384b282ce0cb8431f1a7a2396de92fb66186d8c57df" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "4.10.0" json_schema: dependency: transitive description: name: json_schema - sha256: fc8c3e280c7647ed8e94f2565ba107ef4aff94b096764b8460a7e1ae39f20382 + sha256: f37d9c3fdfe8c9aae55fdfd5af815d24ce63c3a0f6a2c1f0982c30f43643fa1a + url: "https://pub.dev" + source: hosted + version: "5.2.2" + json_serializable: + dependency: transitive + description: + name: json_serializable + sha256: "93fba3ad139dab2b1ce59ecc6fdce6da46a42cdb6c4399ecda30f1e7e725760d" url: "https://pub.dev" source: hosted - version: "5.2.1" + version: "6.12.0" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "11.0.1" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: @@ -452,10 +476,10 @@ packages: dependency: transitive description: name: lints - sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.0" logging: dependency: transitive description: @@ -532,18 +556,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e url: "https://pub.dev" source: hosted - version: "2.2.17" + version: "2.2.22" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.5.1" path_provider_linux: dependency: transitive description: @@ -604,34 +628,34 @@ packages: dependency: "direct main" description: name: polkadart - sha256: c91901620ba4b0de1f80fe406d509a404f3ed3fde059e7483a1a597ee4ab96da + sha256: eff6fdbf48b724602e0b7fe73c741995336adb19364977598fbbce4bfe3b2567 url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "1.1.0" polkadart_cli: dependency: "direct main" description: name: polkadart_cli - sha256: "3c2371134031c518b7728e24fd1b5dbcce98a364c97dd5a89518cccfc7222712" + sha256: "8429c3dc08d3b388178d57357172790fc0e00b8a085f0b90ed99e8146cd9677e" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "1.0.1" polkadart_keyring: dependency: "direct main" description: name: polkadart_keyring - sha256: cb1b9733bfbd603d73410ca68e808b0921e24291cdc02ade18907980c5c6872c + sha256: "302001dfaf87c25de398c4270e827bb97712ef5da095b5fb517967f2e8ba0b0f" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" polkadart_scale_codec: dependency: transitive description: name: polkadart_scale_codec - sha256: "07044bf15d5c02ee79984b2696dcf25c598563eca12e70bc9ff45dd7948d93d5" + sha256: "92164062f66ba3b3e99437bc591f3a9a68c0122518f676061b5d67470db68a12" url: "https://pub.dev" source: hosted - version: "1.6.0" + version: "2.0.1" process: dependency: transitive description: @@ -648,6 +672,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" quiver: dependency: "direct main" description: @@ -676,10 +708,10 @@ packages: dependency: transitive description: name: rfc_6901 - sha256: df1bbfa3d023009598f19636d6114c6ac1e0b7bb7bf6a260f0e6e6ce91416820 + sha256: "6a43b1858dca2febaf93e15639aa6b0c49ccdfd7647775f15a499f872b018154" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.2.1" ristretto255: dependency: transitive description: @@ -699,34 +731,34 @@ packages: dependency: transitive description: name: secp256k1_ecdsa - sha256: e2215e3351ad24b603d856a55814ddc90bc158f3ff25efecde4ab318a71a04a7 + sha256: "137c36cee217c322d8e87ad8dea76e0e976230f26573528bae7bfa301c2f3264" url: "https://pub.dev" source: hosted - version: "0.6.2" + version: "0.6.3" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e" + sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f url: "https://pub.dev" source: hosted - version: "2.4.11" + version: "2.4.20" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.6" shared_preferences_linux: dependency: transitive description: @@ -764,30 +796,46 @@ packages: description: flutter source: sdk version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "1d562a3c1f713904ebbed50d2760217fd8a51ca170ac4b05b0db490699dbac17" + url: "https://pub.dev" + source: hosted + version: "4.2.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "4a85e90b50694e652075cbe4575665539d253e6ec10e46e76b45368ab5e3caae" + url: "https://pub.dev" + source: hosted + version: "1.3.10" source_span: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" sr25519: dependency: transitive description: name: sr25519 - sha256: "38c840abe245d4e777f1b7593d8f72ae463801c8ef9012a00d2d244f3a944fe3" + sha256: "689bbb2c8805b33feae65179c778a8af6c3976029bc5a31d526b88c4749d8138" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" ss58: dependency: "direct main" description: name: ss58 - sha256: ad12bcdc909e73648aba52754b1eab81880bd2cbc4fc6cbaa02695affe49201d + sha256: ba978510735192593ae15fc92fec4c400b23a08fcaa6f156876f97835a18c8cf url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "2.0.1" stack_trace: dependency: transitive description: @@ -824,18 +872,18 @@ packages: dependency: transitive description: name: substrate_bip39 - sha256: "8103aafb10df3b489feaadfc8874dbe2d8474f3deab0383657b4fa5af22fcb39" + sha256: "877cdba1536b6c9bfb2f3821f7d0893bf99be0627573b2944eada48ff4049e15" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" substrate_metadata: dependency: transitive description: name: substrate_metadata - sha256: "252a4a00a23b7da4982d7d2ac5154b6164fd71b3eb0a113faa0ce7983b4e0035" + sha256: "60e57a4b1fdfbe55621d190e356187add54d1a3b26254306404fa91334d0f53a" url: "https://pub.dev" source: hosted - version: "1.6.0" + version: "2.1.0" sync_http: dependency: transitive description: @@ -872,10 +920,10 @@ packages: dependency: transitive description: name: unorm_dart - sha256: "8e3870a1caa60bde8352f9597dd3535d8068613269444f8e35ea8925ec84c1f5" + sha256: "0c69186b03ca6addab0774bcc0f4f17b88d4ce78d9d4d8f0619e30a99ead58e7" url: "https://pub.dev" source: hosted - version: "0.3.1+1" + version: "0.3.2" uri: dependency: transitive description: @@ -884,14 +932,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - utility: - dependency: transitive - description: - name: utility - sha256: "200d264c3804e87da7ea36aa81bd73fb845d2cb7b2e820f3f357a0a2bd4e37f5" - url: "https://pub.dev" - source: hosted - version: "1.0.3" vector_math: dependency: transitive description: @@ -912,10 +952,10 @@ packages: dependency: transitive description: name: watcher - sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.1" web: dependency: transitive description: @@ -960,10 +1000,10 @@ packages: dependency: transitive description: name: win32 - sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e url: "https://pub.dev" source: hosted - version: "5.14.0" + version: "5.15.0" xdg_directories: dependency: transitive description: @@ -989,5 +1029,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0 <4.0.0" - flutter: ">=3.27.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0"