From ee23e0c4fd45d8ed733c336fd00be1276619a6fc Mon Sep 17 00:00:00 2001 From: smk762 Date: Mon, 27 Oct 2025 21:15:43 +0800 Subject: [PATCH 1/3] only use sync_params when asked --- .../logged_in_view_widget.dart | 18 +++++++---- .../instance_manager/zhtlc_config_dialog.dart | 28 ++++++++++------- .../zhtlc_activation_strategy.dart | 27 ++++++++++------- .../activation_config_service.dart | 30 +++++++++++++++---- 4 files changed, 69 insertions(+), 34 deletions(-) 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..435669df 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 @@ -81,17 +81,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..5ef26ea4 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 @@ -44,14 +44,15 @@ class ZhtlcUserConfig { this.scanBlocksPerIteration = 1000, this.scanIntervalMs = 0, this.taskStatusPollingIntervalMs, - this.syncParams, }); final String zcashParamsPath; final int scanBlocksPerIteration; final int scanIntervalMs; final int? taskStatusPollingIntervalMs; - 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 +60,6 @@ class ZhtlcUserConfig { 'scanIntervalMs': scanIntervalMs, if (taskStatusPollingIntervalMs != null) 'taskStatusPollingIntervalMs': taskStatusPollingIntervalMs, - if (syncParams != null) 'syncParams': syncParams!.toJsonRequest(), }; static ZhtlcUserConfig fromJson(JsonMap json) => ZhtlcUserConfig( @@ -70,9 +70,6 @@ class ZhtlcUserConfig { taskStatusPollingIntervalMs: json.valueOrNull( 'taskStatusPollingIntervalMs', ), - syncParams: ZhtlcSyncParams.tryParse( - json.valueOrNull('syncParams'), - ), ); } @@ -194,6 +191,9 @@ class ActivationConfigService { final ActivationConfigRepository repo; final WalletIdResolver _walletIdResolver; + // One-shot sync params coordinator. Not persisted; cleared after use. + final Map<_WalletAssetKey, ZhtlcSyncParams?> _oneShotSyncParams = {}; + Future _requireActiveWallet() async { final walletId = await _walletIdResolver(); if (walletId == null) { @@ -243,6 +243,24 @@ 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; + } + final Map<_WalletAssetKey, Completer> _awaitingControllers = {}; } From 12de137181fc131f2aed1a6fa3a98bffafa233b0 Mon Sep 17 00:00:00 2001 From: smk762 Date: Mon, 27 Oct 2025 21:32:35 +0800 Subject: [PATCH 2/3] allow optional sync params --- .../activation_config/activation_config_service.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 5ef26ea4..ece8ea14 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 @@ -44,12 +44,17 @@ class ZhtlcUserConfig { this.scanBlocksPerIteration = 1000, this.scanIntervalMs = 0, this.taskStatusPollingIntervalMs, + this.syncParams, }); final String zcashParamsPath; 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. @@ -234,6 +239,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); } From 1fa903d3f10643c29fa7c99f784362939cca4316 Mon Sep 17 00:00:00 2001 From: Kadan Stadelmann Date: Mon, 27 Oct 2025 21:59:06 +0100 Subject: [PATCH 3/3] sign out clean-up for one-shot sync params --- .../zhtlc_activation_strategy.dart | 5 +-- .../activation_config_service.dart | 34 ++++++++++++++++++- .../komodo_defi_sdk/lib/src/bootstrap.dart | 1 + .../lib/src/komodo_defi_sdk.dart | 3 ++ 4 files changed, 40 insertions(+), 3 deletions(-) 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 435669df..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; 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 ece8ea14..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; @@ -191,10 +192,28 @@ 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 = {}; @@ -271,6 +290,19 @@ class ActivationConfigService { 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()),