diff --git a/packages/komodo_defi_sdk/example/lib/widgets/instance_manager/logged_in_view_widget.dart b/packages/komodo_defi_sdk/example/lib/widgets/instance_manager/logged_in_view_widget.dart index c314a6b6..7a345cea 100644 --- a/packages/komodo_defi_sdk/example/lib/widgets/instance_manager/logged_in_view_widget.dart +++ b/packages/komodo_defi_sdk/example/lib/widgets/instance_manager/logged_in_view_widget.dart @@ -216,16 +216,22 @@ class _LoggedInViewWidgetState extends State { final existing = await sdk.activationConfigService .getSavedZhtlc(asset.id); if (existing == null && mounted) { - final config = + final dialogResult = await ZhtlcConfigDialogHandler.handleZhtlcConfigDialog( - context, - asset, - ); + context, + asset, + ); if (!mounted) return; - if (config != null) { + if (dialogResult != null) { + if (dialogResult.oneShotSync != null) { + await sdk.activationConfigService.setOneShotSyncParams( + asset.id, + dialogResult.oneShotSync, + ); + } await sdk.activationConfigService.saveZhtlcConfig( asset.id, - config, + dialogResult.config, ); } else { return; // User cancelled diff --git a/packages/komodo_defi_sdk/example/lib/widgets/instance_manager/zhtlc_config_dialog.dart b/packages/komodo_defi_sdk/example/lib/widgets/instance_manager/zhtlc_config_dialog.dart index fd0a69cb..42fe774d 100644 --- a/packages/komodo_defi_sdk/example/lib/widgets/instance_manager/zhtlc_config_dialog.dart +++ b/packages/komodo_defi_sdk/example/lib/widgets/instance_manager/zhtlc_config_dialog.dart @@ -1,15 +1,14 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart' - show ZhtlcSyncParams; import 'package:komodo_defi_sdk/komodo_defi_sdk.dart' show DownloadProgress, DownloadResultPatterns, ZcashParamsDownloader, ZcashParamsDownloaderFactory, - ZhtlcUserConfig; + ZhtlcUserConfig, + ZhtlcSyncParams; import 'package:komodo_defi_types/komodo_defi_types.dart'; /// Handles ZHTLC configuration dialog with optional automatic Zcash parameters download. @@ -19,6 +18,12 @@ import 'package:komodo_defi_types/komodo_defi_types.dart'; /// - Shows progress dialog during download /// - Displays configuration dialog for user input /// - Handles download failures gracefully with fallback to manual configuration +class ZhtlcConfigDialogResult { + ZhtlcConfigDialogResult({required this.config, this.oneShotSync}); + final ZhtlcUserConfig config; + final ZhtlcSyncParams? oneShotSync; +} + class ZhtlcConfigDialogHandler { /// Shows a download progress dialog for Zcash parameters. /// @@ -144,7 +149,7 @@ class ZhtlcConfigDialogHandler { /// On desktop platforms, this method will attempt to download Zcash parameters /// automatically. If successful, it prefills the parameters path in the dialog. /// Returns null if the user cancels the download or configuration. - static Future handleZhtlcConfigDialog( + static Future handleZhtlcConfigDialog( BuildContext context, Asset asset, ) async { @@ -199,7 +204,7 @@ class ZhtlcConfigDialogHandler { /// /// If [prefilledZcashPath] is provided, the Zcash parameters path field /// will be prefilled and made read-only. - static Future _showZhtlcConfigDialog( + static Future _showZhtlcConfigDialog( BuildContext context, Asset asset, { String? prefilledZcashPath, @@ -208,6 +213,7 @@ class ZhtlcConfigDialogHandler { final blocksPerIterController = TextEditingController(text: '1000'); final intervalMsController = TextEditingController(text: '0'); + // Sync params controls (used for initial sync and intentional rewinds) var syncType = 'date'; // earliest | height | date final syncValueController = TextEditingController(); DateTime? selectedDateTime; @@ -225,13 +231,11 @@ class ZhtlcConfigDialogHandler { ); if (picked != null) { - // Default to midnight (00:00) of the selected day selectedDateTime = DateTime(picked.year, picked.month, picked.day); syncValueController.text = formatDate(selectedDateTime!); } } - // Initialize with default date (2 days ago) void initializeDate() { selectedDateTime = DateTime.now().subtract(const Duration(days: 2)); syncValueController.text = formatDate(selectedDateTime!); @@ -239,7 +243,7 @@ class ZhtlcConfigDialogHandler { initializeDate(); - ZhtlcUserConfig? result; + ZhtlcConfigDialogResult? result; await showDialog( context: context, @@ -375,19 +379,21 @@ class ZhtlcConfigDialogHandler { ); return; } - // Convert to Unix timestamp (seconds since epoch) final unixTimestamp = selectedDateTime!.millisecondsSinceEpoch ~/ 1000; syncParams = ZhtlcSyncParams.date(unixTimestamp); } - result = ZhtlcUserConfig( + final config = ZhtlcUserConfig( zcashParamsPath: path, scanBlocksPerIteration: int.tryParse(blocksPerIterController.text) ?? 1000, scanIntervalMs: int.tryParse(intervalMsController.text) ?? 0, - syncParams: syncParams, + ); + result = ZhtlcConfigDialogResult( + config: config, + oneShotSync: syncParams, ); Navigator.of(context).pop(); }, diff --git a/packages/komodo_defi_sdk/lib/src/activation/protocol_strategies/zhtlc_activation_strategy.dart b/packages/komodo_defi_sdk/lib/src/activation/protocol_strategies/zhtlc_activation_strategy.dart index 2afeff84..123e01ba 100644 --- a/packages/komodo_defi_sdk/lib/src/activation/protocol_strategies/zhtlc_activation_strategy.dart +++ b/packages/komodo_defi_sdk/lib/src/activation/protocol_strategies/zhtlc_activation_strategy.dart @@ -1,5 +1,6 @@ -// TODO(komodo-team): Allow passing the start sync mode; currently hard-coded -// to sync from the time of activation. +// Start sync mode can be passed via one-shot sync params through +// ActivationConfigService.setOneShotSyncParams() before activation. +// See zhtlc_config_dialog.dart for UI implementation. import 'dart:convert'; import 'dart:developer' show log; @@ -81,17 +82,22 @@ class ZhtlcActivationStrategy extends ProtocolActivationStrategy { privKeyPolicy: privKeyPolicy, ); - // Apply sync params if provided by the user configuration via rpc_data - if (params.mode?.rpcData != null && userConfig.syncParams != null) { - final rpcData = params.mode!.rpcData!; - final updatedRpcData = ActivationRpcData( - lightWalletDServers: rpcData.lightWalletDServers, - electrum: rpcData.electrum, - syncParams: userConfig.syncParams, - ); - params = params.copyWith( - mode: ActivationMode(rpc: params.mode!.rpc, rpcData: updatedRpcData), - ); + // Apply one-shot sync_params only when explicitly provided via config form + // right before activation. This avoids caching and unintended rewinds. + if (params.mode?.rpcData != null) { + final oneShotSync = await configService.takeOneShotSyncParams(asset.id); + if (oneShotSync != null) { + final rpcData = params.mode!.rpcData!; + final updatedRpcData = ActivationRpcData( + lightWalletDServers: rpcData.lightWalletDServers, + electrum: rpcData.electrum, + syncParams: oneShotSync, + ); + params = params.copyWith( + mode: + ActivationMode(rpc: params.mode!.rpc, rpcData: updatedRpcData), + ); + } } yield ZhtlcActivationProgress.validation(protocol); diff --git a/packages/komodo_defi_sdk/lib/src/activation_config/activation_config_service.dart b/packages/komodo_defi_sdk/lib/src/activation_config/activation_config_service.dart index 978347c5..f783a103 100644 --- a/packages/komodo_defi_sdk/lib/src/activation_config/activation_config_service.dart +++ b/packages/komodo_defi_sdk/lib/src/activation_config/activation_config_service.dart @@ -5,6 +5,7 @@ import 'package:hive_ce/hive.dart'; import 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart'; import 'package:komodo_defi_types/komodo_defi_type_utils.dart'; import 'package:komodo_defi_types/komodo_defi_types.dart'; +import 'package:komodo_defi_local_auth/komodo_defi_local_auth.dart'; typedef JsonMap = Map; @@ -51,7 +52,13 @@ class ZhtlcUserConfig { final int scanBlocksPerIteration; final int scanIntervalMs; final int? taskStatusPollingIntervalMs; + /// Optional, accepted for backward compatibility. Not persisted. + /// If provided to saveZhtlcConfig, it will be applied as a one-shot + /// sync override for the next activation and then discarded. final ZhtlcSyncParams? syncParams; + // Sync params are no longer persisted here; they are supplied one-shot + // via ActivationConfigService at activation time when the user requests + // an intentional resync. JsonMap toJson() => { 'zcashParamsPath': zcashParamsPath, @@ -59,7 +66,6 @@ class ZhtlcUserConfig { 'scanIntervalMs': scanIntervalMs, if (taskStatusPollingIntervalMs != null) 'taskStatusPollingIntervalMs': taskStatusPollingIntervalMs, - if (syncParams != null) 'syncParams': syncParams!.toJsonRequest(), }; static ZhtlcUserConfig fromJson(JsonMap json) => ZhtlcUserConfig( @@ -70,9 +76,6 @@ class ZhtlcUserConfig { taskStatusPollingIntervalMs: json.valueOrNull( 'taskStatusPollingIntervalMs', ), - syncParams: ZhtlcSyncParams.tryParse( - json.valueOrNull('syncParams'), - ), ); } @@ -189,10 +192,31 @@ class ActivationConfigService { ActivationConfigService( this.repo, { required WalletIdResolver walletIdResolver, - }) : _walletIdResolver = walletIdResolver; + Stream? authStateChanges, + }) : _walletIdResolver = walletIdResolver { + // Listen to auth state changes to clear one-shot params on sign-out + _authStateSubscription = authStateChanges?.listen((user) { + if (user == null) { + // User signed out, clear all one-shot params + _oneShotSyncParams.clear(); + } else { + // User signed in or changed, clear one-shot params for previous wallet + // if it was different from the current one + if (_lastWalletId != null && _lastWalletId != user.walletId) { + clearOneShotSyncParamsForWallet(_lastWalletId!); + } + _lastWalletId = user.walletId; + } + }); + } final ActivationConfigRepository repo; final WalletIdResolver _walletIdResolver; + StreamSubscription? _authStateSubscription; + WalletId? _lastWalletId; + + // One-shot sync params coordinator. Not persisted; cleared after use. + final Map<_WalletAssetKey, ZhtlcSyncParams?> _oneShotSyncParams = {}; Future _requireActiveWallet() async { final walletId = await _walletIdResolver(); @@ -234,6 +258,11 @@ class ActivationConfigService { Future saveZhtlcConfig(AssetId id, ZhtlcUserConfig config) async { final walletId = await _requireActiveWallet(); + // If legacy callers provide syncParams in the config, convert it to + // a one-shot sync override and do not persist it. + if (config.syncParams != null) { + _oneShotSyncParams[_WalletAssetKey(walletId, id)] = config.syncParams; + } await repo.saveConfig(walletId, id, config); } @@ -243,6 +272,37 @@ class ActivationConfigService { _awaitingControllers[_WalletAssetKey(walletId, id)]?.complete(config); } + /// Sets a one-shot sync params value for the next activation of [id]. + /// This is not persisted and will be consumed and cleared on activation. + Future setOneShotSyncParams( + AssetId id, + ZhtlcSyncParams? syncParams, + ) async { + final walletId = await _requireActiveWallet(); + _oneShotSyncParams[_WalletAssetKey(walletId, id)] = syncParams; + } + + /// Returns and clears any pending one-shot sync params for [id]. + Future takeOneShotSyncParams(AssetId id) async { + final walletId = await _requireActiveWallet(); + final key = _WalletAssetKey(walletId, id); + final value = _oneShotSyncParams.remove(key); + return value; + } + + /// Clears all one-shot sync params for the specified wallet. + /// This should be called when a user signs out to prevent stale one-shot + /// params from being applied on the next activation after re-login. + void clearOneShotSyncParamsForWallet(WalletId walletId) { + _oneShotSyncParams.removeWhere((key, _) => key.walletId == walletId); + } + + /// Disposes of the service and cleans up resources. + void dispose() { + _authStateSubscription?.cancel(); + _authStateSubscription = null; + } + final Map<_WalletAssetKey, Completer> _awaitingControllers = {}; } diff --git a/packages/komodo_defi_sdk/lib/src/bootstrap.dart b/packages/komodo_defi_sdk/lib/src/bootstrap.dart index 617aa917..9a60ed5b 100644 --- a/packages/komodo_defi_sdk/lib/src/bootstrap.dart +++ b/packages/komodo_defi_sdk/lib/src/bootstrap.dart @@ -88,6 +88,7 @@ Future bootstrap({ return ActivationConfigService( repo, walletIdResolver: () async => (await auth.currentUser)?.walletId, + authStateChanges: auth.authStateChanges, ); }, dependsOn: [KomodoDefiLocalAuth]); diff --git a/packages/komodo_defi_sdk/lib/src/komodo_defi_sdk.dart b/packages/komodo_defi_sdk/lib/src/komodo_defi_sdk.dart index db979820..f9330005 100644 --- a/packages/komodo_defi_sdk/lib/src/komodo_defi_sdk.dart +++ b/packages/komodo_defi_sdk/lib/src/komodo_defi_sdk.dart @@ -408,6 +408,9 @@ class KomodoDefiSdk with SecureRpcPasswordMixin { _disposeIfRegistered((m) => m.dispose()), _disposeIfRegistered((m) => m.dispose()), _disposeIfRegistered((m) => m.dispose()), + _disposeIfRegistered( + (m) async => m.dispose(), + ), _disposeIfRegistered((m) => m.dispose()), _disposeIfRegistered((m) => m.dispose()), _disposeIfRegistered((m) => m.dispose()),