Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -216,16 +216,22 @@ class _LoggedInViewWidgetState extends State<LoggedInViewWidget> {
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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
///
Expand Down Expand Up @@ -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<ZhtlcUserConfig?> handleZhtlcConfigDialog(
static Future<ZhtlcConfigDialogResult?> handleZhtlcConfigDialog(
BuildContext context,
Asset asset,
) async {
Expand Down Expand Up @@ -199,7 +204,7 @@ class ZhtlcConfigDialogHandler {
///
/// If [prefilledZcashPath] is provided, the Zcash parameters path field
/// will be prefilled and made read-only.
static Future<ZhtlcUserConfig?> _showZhtlcConfigDialog(
static Future<ZhtlcConfigDialogResult?> _showZhtlcConfigDialog(
BuildContext context,
Asset asset, {
String? prefilledZcashPath,
Expand All @@ -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;
Expand All @@ -225,21 +231,19 @@ 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!);
}

initializeDate();

ZhtlcUserConfig? result;
ZhtlcConfigDialogResult? result;

await showDialog<void>(
context: context,
Expand Down Expand Up @@ -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();
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, dynamic>;

Expand Down Expand Up @@ -51,15 +52,20 @@ 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,
'scanBlocksPerIteration': scanBlocksPerIteration,
'scanIntervalMs': scanIntervalMs,
if (taskStatusPollingIntervalMs != null)
'taskStatusPollingIntervalMs': taskStatusPollingIntervalMs,
if (syncParams != null) 'syncParams': syncParams!.toJsonRequest(),
};

static ZhtlcUserConfig fromJson(JsonMap json) => ZhtlcUserConfig(
Expand All @@ -70,9 +76,6 @@ class ZhtlcUserConfig {
taskStatusPollingIntervalMs: json.valueOrNull<int>(
'taskStatusPollingIntervalMs',
),
syncParams: ZhtlcSyncParams.tryParse(
json.valueOrNull<dynamic>('syncParams'),
),
);
}

Expand Down Expand Up @@ -189,10 +192,31 @@ class ActivationConfigService {
ActivationConfigService(
this.repo, {
required WalletIdResolver walletIdResolver,
}) : _walletIdResolver = walletIdResolver;
Stream<KdfUser?>? 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<KdfUser?>? _authStateSubscription;
WalletId? _lastWalletId;

// One-shot sync params coordinator. Not persisted; cleared after use.
final Map<_WalletAssetKey, ZhtlcSyncParams?> _oneShotSyncParams = {};

Future<WalletId> _requireActiveWallet() async {
final walletId = await _walletIdResolver();
Expand Down Expand Up @@ -234,6 +258,11 @@ class ActivationConfigService {

Future<void> 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);
}

Expand All @@ -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<void> 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<ZhtlcSyncParams?> 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<ZhtlcUserConfig?>> _awaitingControllers =
{};
}
Expand Down
1 change: 1 addition & 0 deletions packages/komodo_defi_sdk/lib/src/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Future<void> bootstrap({
return ActivationConfigService(
repo,
walletIdResolver: () async => (await auth.currentUser)?.walletId,
authStateChanges: auth.authStateChanges,
);
}, dependsOn: [KomodoDefiLocalAuth]);

Expand Down
3 changes: 3 additions & 0 deletions packages/komodo_defi_sdk/lib/src/komodo_defi_sdk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,9 @@ class KomodoDefiSdk with SecureRpcPasswordMixin {
_disposeIfRegistered<KomodoDefiLocalAuth>((m) => m.dispose()),
_disposeIfRegistered<AssetManager>((m) => m.dispose()),
_disposeIfRegistered<ActivationManager>((m) => m.dispose()),
_disposeIfRegistered<ActivationConfigService>(
(m) async => m.dispose(),
),
_disposeIfRegistered<BalanceManager>((m) => m.dispose()),
_disposeIfRegistered<PubkeyManager>((m) => m.dispose()),
_disposeIfRegistered<TransactionHistoryManager>((m) => m.dispose()),
Expand Down
Loading