From 3b16f2fb7c9a7686bf365a0d1c88c65ff530bccc Mon Sep 17 00:00:00 2001 From: cyan Date: Tue, 11 Aug 2026 03:15:59 +0200 Subject: [PATCH] fix: zcash address for exchange (#3491) * fix: zcash address for exchange * fix: honour the restore height the user entered - Make Zcash rescan work in both directions by resetting sync from the new birth height - Keep the entered height when restoring a wallet from keys - Tolerate grouping separators in typed restore heights --------- Co-authored-by: Vik Sharma --- cw_zcash/lib/src/zcash_wallet.dart | 45 ++++++++++++++++++- cw_zcash/lib/src/zcash_wallet_addresses.dart | 8 ++++ .../screens/restore/wallet_restore_page.dart | 4 ++ lib/src/widgets/blockchain_height_widget.dart | 3 +- .../exchange/exchange_view_model.dart | 13 ++++++ 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/cw_zcash/lib/src/zcash_wallet.dart b/cw_zcash/lib/src/zcash_wallet.dart index 6266c6700..34ae0f1e3 100644 --- a/cw_zcash/lib/src/zcash_wallet.dart +++ b/cw_zcash/lib/src/zcash_wallet.dart @@ -1021,7 +1021,50 @@ abstract class ZcashWalletBase @override @action Future rescan({required final int height}) async { - await zkool_sync.rewindSync(height: height, account: accountId, c: c); + syncStatus = StartingScanSyncStatus(height); + try { + await zkool_sync.cancelSync(); + isSyncing = false; + + await runWithCoin( + accountId: accountId, + func: (final coin) async { + await zkool_account.updateAccount( + update: zkool_account.AccountUpdate( + coin: coin.coin, + id: accountId, + birth: height, + folder: 0, + ), + c: coin, + ); + + final accounts = await zkool_account.listAccounts(c: coin); + final updated = + accounts.where((final a) => a.id == accountId).firstOrNull; + if (updated == null) { + throw Exception('account $accountId not found after update'); + } + if (updated.birth != height) { + throw Exception( + 'birth height did not persist: wanted $height, ' + 'database still has ${updated.birth}', + ); + } + + await zkool_account.resetSync(id: accountId, c: coin); + }, + ); + + lastKnownRestoreHeight = height; + await save(); + + syncStatus = ConnectedSyncStatus(); + unawaited(startSync()); + } catch (e) { + printV('Zcash rescan failed: $e'); + syncStatus = FailedSyncStatus(error: e.toString()); + } // try { // syncStatus = StartingScanSyncStatus(height); // printV("rescanning from: $height"); diff --git a/cw_zcash/lib/src/zcash_wallet_addresses.dart b/cw_zcash/lib/src/zcash_wallet_addresses.dart index 295f2b096..d2b831300 100644 --- a/cw_zcash/lib/src/zcash_wallet_addresses.dart +++ b/cw_zcash/lib/src/zcash_wallet_addresses.dart @@ -220,6 +220,13 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store { }; await _syncRotationHiddenAddresses(); await _applyAddressForCurrentType(); + await _persistAddressForExchange(); + } + + Future _persistAddressForExchange() async { + final addr = addressForExchange; + if (isPlaceholderAddress(addr)) return; + await walletInfo.setAddresses({addr: 'transparent'}); } Future _applyAddressForCurrentType() async { @@ -253,6 +260,7 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store { walletInfo.address = address; } await walletInfo.setAddresses({}); + await _persistAddressForExchange(); await walletInfo.setAddressInfos(addressInfos); await walletInfo.setUsedAddresses(usedAddresses.toList()); await walletInfo.setHiddenAddresses(hiddenAddresses.toList()); diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index de2157bee..3e5ed20aa 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -215,6 +215,10 @@ class WalletRestorePage extends BasePage { walletRestoreFromKeysFormKey.currentState!.privateKeyController.text; credentials['name'] = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text; + if (walletRestoreViewModel.hasBlockchainHeightSelector) { + credentials['height'] = + walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState?.height; + } } else { credentials['name'] = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text; diff --git a/lib/src/widgets/blockchain_height_widget.dart b/lib/src/widgets/blockchain_height_widget.dart index c4cafb5a2..273db827f 100644 --- a/lib/src/widgets/blockchain_height_widget.dart +++ b/lib/src/widgets/blockchain_height_widget.dart @@ -65,7 +65,8 @@ class BlockchainHeightState extends State { try { final int _height; if (restoreHeightController.text.isNotEmpty) { - _height = int.tryParse(restoreHeightController.text) ?? 0; + final digits = restoreHeightController.text.replaceAll(RegExp(r'[^0-9]'), ''); + _height = int.tryParse(digits) ?? 0; } else { _height = 0; } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 84770a3ad..595d70d47 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -1185,6 +1185,19 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with } } + if (receiveCurrency == CryptoCurrency.zec && + selectedAddressBookWallet?.type == WalletType.zcash) { + if (wallet.type == WalletType.zcash && wallet.name == selectedAddressBookWallet!.name) { + receiveAddress = wallet.walletAddresses.addressForExchange; + } else { + receiveAddress = (await selectedAddressBookWallet!.getAddresses()) + .entries + .firstWhereOrNull((e) => e.value == 'transparent') + ?.key ?? + receiveAddress; + } + } + // parse Lightning address to bolt11 invoice if (receiveAddress.contains("@")) { receiveAddress = await getBolt11FromLightingAddress(receiveAddress) ?? receiveAddress;