fetch params from download.z.cash on demand to reduce app size (#2879)

add restore height to seed screen

cyan committed Feb 10, 2026 at 00:44 UTC 6b458e8a3b3e42af75280ddfd4f5ce7a56d6fb2d
4 files changed +52 -6
cw_zcash/lib/src/zcash_taddress_rotation.dart
+1
@@ -313,6 +313,7 @@ class ZcashTaddressRotation {
313
314 final recipient = Recipient(recipientBuilder.toBytes());
315 final fee = FeeT(fee: 10000, minFee: 0, maxFee: 0, scheme: 0);
316 + await ZcashWalletBase.loadProver();
317 final txPlan = await ZcashWalletService.runInDbMutex(
318 () => WarpApi.prepareTx(
319 coin,
cw_zcash/lib/src/zcash_wallet.dart
+44 -4
@@ -11,6 +11,7 @@ import 'package:cw_core/sync_status.dart';
11 import 'package:cw_core/transaction_direction.dart';
12 import 'package:cw_core/transaction_priority.dart';
13 import 'package:cw_core/utils/print_verbose.dart';
14 +import 'package:cw_core/utils/proxy_wrapper.dart';
15 import 'package:cw_core/wallet_addresses.dart';
16 import 'package:cw_core/wallet_base.dart';
17 import 'package:cw_core/wallet_credentials.dart';
@@ -220,6 +221,7 @@ abstract class ZcashWalletBase
221
222 // pools parameter: bitmask for which pools to use for sending
223 // 1=Transparent, 2=Sapling, 4=Orchard, 7=All pools
224 + await ZcashWalletBase.loadProver();
225 // Using 7 (all pools) allows spending from any pool type
226 final txPlan = await ZcashWalletService.runInDbMutex(
227 () => WarpApi.prepareTx(
@@ -333,6 +335,8 @@ abstract class ZcashWalletBase
335 "privateViewKey": backup.fvk,
336 "uvk": backup.uvk,
337 "tsk": backup.tsk,
338 + if (lastKnownRestoreHeight != null)
339 + "restoreHeight": lastKnownRestoreHeight.toString(),
340 };
341 }
342
@@ -354,12 +358,14 @@ abstract class ZcashWalletBase
358 bool get hasRescan => true;
359
360 static Future<void> storeZcashHeight(final int height) async {
361 + lastKnownRestoreHeight = height;
362 final zcashDir = await pathForWalletTypeDir(type: WalletType.zcash);
363 final zcashInitialSync = File(p.join(zcashDir, ".initial-sync-marker"));
364 zcashInitialSync.writeAsBytesSync([0x00]);
365 zcashInitialSync.writeAsStringSync(height.toString(), mode: FileMode.writeOnlyAppend);
366 }
367
368 + static int? lastKnownRestoreHeight = null;
369 static Future<int?> loadZcashHeight() async {
370 final zcashDir = await pathForWalletTypeDir(type: WalletType.zcash);
371 final zcashInitialSync = File(p.join(zcashDir, ".initial-sync-marker"));
@@ -371,7 +377,8 @@ abstract class ZcashWalletBase
377 return null;
378 }
379 final heightString = String.fromCharCodes(bytes.skip(1));
374 - return int.tryParse(heightString);
380 + lastKnownRestoreHeight = int.tryParse(heightString);
381 + return lastKnownRestoreHeight;
382 }
383
384 static int zashiAnnouncedBlockHeight = 2419420;
@@ -749,6 +756,7 @@ abstract class ZcashWalletBase
756
757 final recipient = Recipient(recipientBuilder.toBytes());
758 final fee = FeeT(fee: 10000, minFee: 0, maxFee: 0, scheme: 0);
759 + await ZcashWalletBase.loadProver();
760 final txPlan = await ZcashWalletService.runInDbMutex(
761 () => WarpApi.prepareTx(
762 coin,
@@ -1039,13 +1047,45 @@ abstract class ZcashWalletBase
1047 } catch (e) {
1048 printV("zec init failed: $e");
1049 } // do not fail on network exception
1042 - final spend = await rootBundle.load('scripts/zcash_lib/assets/sapling-spend.params');
1043 - final output = await rootBundle.load('scripts/zcash_lib/assets/sapling-output.params');
1044 - WarpApi.initProver(spend.buffer.asUint8List(), output.buffer.asUint8List());
1050 + await loadZcashHeight();
1051 +
1052 + unawaited(loadProver());
1053 +
1054 await ZcashTaddressRotation.init();
1055 await ZcashTransactionInfo.init();
1056 _initialized = true;
1057 }
1058 +
1059 + static bool isProverLoaded = false;
1060 + static Future<void> loadProver() async {
1061 + Uint8List? spend;
1062 + Uint8List? output;
1063 + final cacheDir = await getApplicationCacheDirectory();
1064 + try {
1065 + final spendBundle = await rootBundle.load('scripts/zcash_lib/assets/sapling-spend.params');
1066 + final outputBundle = await rootBundle.load('scripts/zcash_lib/assets/sapling-output.params');
1067 + spend = spendBundle.buffer.asUint8List();
1068 + output = outputBundle.buffer.asUint8List();
1069 + if (spend.length == 0 || output.length == 0) throw Exception("NUH UH");
1070 + spend = await File(cacheDir.path+"/sapling-spend.params").readAsBytesSync();
1071 + output = await File(cacheDir.path+"/sapling-output.params").readAsBytesSync();
1072 + if (spend.length == 0 || output.length == 0) throw Exception("NUH UH");
1073 + } catch (e) {
1074 + printV("$e. Fine, I'll download them.");
1075 + final spendResponse = await ProxyWrapper().get(
1076 + clearnetUri: Uri.parse("https://download.z.cash/downloads/sapling-spend.params"),
1077 + );
1078 + final outputResponse = await ProxyWrapper().get(
1079 + clearnetUri: Uri.parse("https://download.z.cash/downloads/sapling-output.params"),
1080 + );
1081 + spend = spendResponse.bodyBytes;
1082 + output = outputResponse.bodyBytes;
1083 + await File(cacheDir.path+"/sapling-spend.params").writeAsBytes(spend);
1084 + await File(cacheDir.path+"/sapling-output.params").writeAsBytes(output);
1085 + }
1086 + WarpApi.initProver(spend, output);
1087 + isProverLoaded = true;
1088 + }
1089
1090 static Future<int> getBlockHeightByTime(final DateTime time) async {
1091 final genesisTime = DateTime.utc(2016, 10, 28);
lib/src/screens/wallet_keys/wallet_keys_page.dart
+1 -1
@@ -183,7 +183,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
183 Widget _buildSeedTab(BuildContext context, bool isLegacySeed) {
184 return Column(
185 children: [
186 - if (isLegacySeedOnly || isLegacySeed || widget.walletKeysViewModel.isBitcoin) ...[
186 + if (isLegacySeedOnly || isLegacySeed ||widget.walletKeysViewModel.shouldShowHeightBox) ...[
187 _buildHeightBox(),
188 const SizedBox(height: 20),
189 ],
lib/view_model/wallet_keys_view_model.dart
+6 -1
@@ -62,7 +62,9 @@ abstract class WalletKeysViewModelBase with Store {
62 }
63
64 bool get isBitcoin => _wallet.type == WalletType.bitcoin;
65 -
65 +
66 + // this is incomplete, needs legacy seed toggle for XMR
67 + bool get shouldShowHeightBox => [WalletType.bitcoin, WalletType.zcash].contains(_wallet.type);
68 final ObservableList<StandartListItem> items;
69
70 @observable
@@ -318,6 +320,9 @@ abstract class WalletKeysViewModelBase with Store {
320 if (_wallet.type == WalletType.wownero) {
321 return wownero!.getRestoreHeight(_wallet)?.toString();
322 }
323 + if (_wallet.type == WalletType.zcash) {
324 + return zcash!.getKeys(_wallet)["restoreHeight"]?.toString();
325 + }
326 if (_restoreHeightByTransactions != 0)
327 return getRoundedRestoreHeight(_restoreHeightByTransactions);
328 if (_restoreHeight != 0) return _restoreHeight.toString();