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 <vik@cakewallet.com>

cyan committed Aug 11, 2026 at 03:15 UTC 3b16f2fb7c9a7686bf365a0d1c88c65ff530bccc
5 files changed +71 -2
cw_zcash/lib/src/zcash_wallet.dart
+44 -1
@@ -1021,7 +1021,50 @@ abstract class ZcashWalletBase
1021 @override
1022 @action
1023 Future<void> rescan({required final int height}) async {
1024 - await zkool_sync.rewindSync(height: height, account: accountId, c: c);
1024 + syncStatus = StartingScanSyncStatus(height);
1025 + try {
1026 + await zkool_sync.cancelSync();
1027 + isSyncing = false;
1028 +
1029 + await runWithCoin(
1030 + accountId: accountId,
1031 + func: (final coin) async {
1032 + await zkool_account.updateAccount(
1033 + update: zkool_account.AccountUpdate(
1034 + coin: coin.coin,
1035 + id: accountId,
1036 + birth: height,
1037 + folder: 0,
1038 + ),
1039 + c: coin,
1040 + );
1041 +
1042 + final accounts = await zkool_account.listAccounts(c: coin);
1043 + final updated =
1044 + accounts.where((final a) => a.id == accountId).firstOrNull;
1045 + if (updated == null) {
1046 + throw Exception('account $accountId not found after update');
1047 + }
1048 + if (updated.birth != height) {
1049 + throw Exception(
1050 + 'birth height did not persist: wanted $height, '
1051 + 'database still has ${updated.birth}',
1052 + );
1053 + }
1054 +
1055 + await zkool_account.resetSync(id: accountId, c: coin);
1056 + },
1057 + );
1058 +
1059 + lastKnownRestoreHeight = height;
1060 + await save();
1061 +
1062 + syncStatus = ConnectedSyncStatus();
1063 + unawaited(startSync());
1064 + } catch (e) {
1065 + printV('Zcash rescan failed: $e');
1066 + syncStatus = FailedSyncStatus(error: e.toString());
1067 + }
1068 // try {
1069 // syncStatus = StartingScanSyncStatus(height);
1070 // printV("rescanning from: $height");
cw_zcash/lib/src/zcash_wallet_addresses.dart
+8
@@ -220,6 +220,13 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
220 };
221 await _syncRotationHiddenAddresses();
222 await _applyAddressForCurrentType();
223 + await _persistAddressForExchange();
224 + }
225 +
226 + Future<void> _persistAddressForExchange() async {
227 + final addr = addressForExchange;
228 + if (isPlaceholderAddress(addr)) return;
229 + await walletInfo.setAddresses({addr: 'transparent'});
230 }
231
232 Future<void> _applyAddressForCurrentType() async {
@@ -253,6 +260,7 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
260 walletInfo.address = address;
261 }
262 await walletInfo.setAddresses({});
263 + await _persistAddressForExchange();
264 await walletInfo.setAddressInfos(addressInfos);
265 await walletInfo.setUsedAddresses(usedAddresses.toList());
266 await walletInfo.setHiddenAddresses(hiddenAddresses.toList());
lib/src/screens/restore/wallet_restore_page.dart
+4
@@ -215,6 +215,10 @@ class WalletRestorePage extends BasePage {
215 walletRestoreFromKeysFormKey.currentState!.privateKeyController.text;
216 credentials['name'] =
217 walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
218 + if (walletRestoreViewModel.hasBlockchainHeightSelector) {
219 + credentials['height'] =
220 + walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState?.height;
221 + }
222 } else {
223 credentials['name'] =
224 walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
lib/src/widgets/blockchain_height_widget.dart
+2 -1
@@ -65,7 +65,8 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
65 try {
66 final int _height;
67 if (restoreHeightController.text.isNotEmpty) {
68 - _height = int.tryParse(restoreHeightController.text) ?? 0;
68 + final digits = restoreHeightController.text.replaceAll(RegExp(r'[^0-9]'), '');
69 + _height = int.tryParse(digits) ?? 0;
70 } else {
71 _height = 0;
72 }
lib/view_model/exchange/exchange_view_model.dart
+13
@@ -1185,6 +1185,19 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
1185 }
1186 }
1187
1188 + if (receiveCurrency == CryptoCurrency.zec &&
1189 + selectedAddressBookWallet?.type == WalletType.zcash) {
1190 + if (wallet.type == WalletType.zcash && wallet.name == selectedAddressBookWallet!.name) {
1191 + receiveAddress = wallet.walletAddresses.addressForExchange;
1192 + } else {
1193 + receiveAddress = (await selectedAddressBookWallet!.getAddresses())
1194 + .entries
1195 + .firstWhereOrNull((e) => e.value == 'transparent')
1196 + ?.key ??
1197 + receiveAddress;
1198 + }
1199 + }
1200 +
1201 // parse Lightning address to bolt11 invoice
1202 if (receiveAddress.contains("@")) {
1203 receiveAddress = await getBolt11FromLightingAddress(receiveAddress) ?? receiveAddress;