feat: zashi zkool sweep, and minor fixes (#2838)

feat: restore from keys zcash
fix: don't display empty keys
This commit is contained in:
cyan
2026-02-06 02:39:19 +01:00
committed by GitHub
parent 4ae94db3ee
commit d8a6766090
59 changed files with 886 additions and 93 deletions
+1
View File
@@ -65,6 +65,7 @@ class PreferencesKey {
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
static const mwebCardDisplay = 'mwebCardDisplay';
static const showZcashMissingFundsCard = 'showZcashMissingFundsCard';
static const mwebEnabled = 'mwebEnabled';
static const hasEnabledMwebBefore = 'hasEnabledMwebBefore';
static const mwebAlwaysScan = 'mwebAlwaysScan';
@@ -333,6 +333,36 @@ class CryptoBalanceWidget extends StatelessWidget {
),
),
),
],
if (dashboardViewModel.showZcashMissingFundsCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: InfoCard(
title: S.of(context).zcash_card_missing_funds,
description: S.of(context).zcash_card_description,
leftButtonTitle: S.of(context).zcash_card_dismiss,
rightButtonTitle: S.of(context).zcash_card_scan,
image: 'assets/images/zec_icon.png',
leftButtonAction: () => _dismissZcash(context),
rightButtonAction: () => _rescanInternalChangeZcash(context),
hintWidget: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse("https://docs.cakewallet.com/tutorials/zashi/#creating-a-new-zcash-wallet-on-cake"),
mode: LaunchMode.externalApplication,
),
child: Text(
S.of(context).learn_more,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
softWrap: true,
),
),
),
),
],
if (dashboardViewModel.showDecredInfoCard) ...[
SizedBox(height: 10),
@@ -462,6 +492,22 @@ class CryptoBalanceWidget extends StatelessWidget {
}
dashboardViewModel.setMwebEnabled();
}
Future<void> _rescanInternalChangeZcash(BuildContext context) async {
dashboardViewModel.rescanInternalChangeZcash();
await showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).alert_notice,
alertContent: S.of(context).zcash_card_warning,
buttonText: S.of(context).understand,
buttonAction: () {
Navigator.of(context).pop();
},
),
);
}
Future<void> _dismissMweb(BuildContext context) async {
await showPopUp<void>(
@@ -476,4 +522,18 @@ class CryptoBalanceWidget extends StatelessWidget {
));
dashboardViewModel.dismissMweb();
}
Future<void> _dismissZcash(BuildContext context) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).alert_notice,
alertContent: S.of(context).zcash_card_enable_later,
buttonText: S.of(context).understand,
buttonAction: () {
Navigator.of(context).pop();
},
));
dashboardViewModel.dismissZcash();
}
}
@@ -232,14 +232,26 @@ class WalletRestoreFromKeysFormState extends State<WalletRestoreFromKeysForm> {
bool nanoBased = widget.walletRestoreViewModel.type == WalletType.nano ||
widget.walletRestoreViewModel.type == WalletType.banano;
return AddressTextField(
addressKey: ValueKey('wallet_restore_from_key_private_key_textfield_key'),
controller: privateKeyController,
placeholder: nanoBased ? S.of(context).seed_hex_form : S.of(context).private_key,
options: [AddressTextFieldOption.paste],
onPushPasteButton: (_) {
_pasteText();
},
return Column(
children: [
AddressTextField(
addressKey: ValueKey('wallet_restore_from_key_private_key_textfield_key'),
controller: privateKeyController,
placeholder: nanoBased ? S.of(context).seed_hex_form : S.of(context).private_key,
options: [AddressTextFieldOption.paste],
onPushPasteButton: (_) {
_pasteText();
},
),
if (widget.walletRestoreViewModel.hasBlockchainHeightSelector)
BlockchainHeightWidget(
key: blockchainHeightKey,
hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven,
onHeightChange: (_) => null,
onHeightOrDateEntered: widget.onHeightOrDateEntered,
walletType: widget.walletRestoreViewModel.type,
),
]
);
}
@@ -50,6 +50,13 @@ class DisplaySettingsPage extends BasePage {
_displaySettingsViewModel.setShowAddressBookPopup(value);
},
),
SettingsSwitcherCell(
title: S.of(context).show_zcash_card,
value: _displaySettingsViewModel.showZcashCard,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setShowZcashCard(value);
},
),
SettingsPickerCell<SyncStatusDisplayMode>(
title: S.current.sync_status_display_mode,
items: SyncStatusDisplayMode.values.toList(),
+12
View File
@@ -133,6 +133,7 @@ abstract class SettingsStoreBase with Store {
required this.silentPaymentsCardDisplay,
required this.mwebAlwaysScan,
required this.mwebCardDisplay,
required this.showZcashMissingFundsCard,
required this.mwebEnabled,
required this.hasEnabledMwebBefore,
required this.mwebNodeUri,
@@ -660,6 +661,11 @@ abstract class SettingsStoreBase with Store {
(bool mwebCardDisplay) =>
_sharedPreferences.setBool(PreferencesKey.mwebCardDisplay, mwebCardDisplay));
reaction(
(_) => showZcashMissingFundsCard,
(bool showZcashMissingFundsCard) =>
_sharedPreferences.setBool(PreferencesKey.showZcashMissingFundsCard, showZcashMissingFundsCard));
reaction((_) => mwebEnabled,
(bool mwebEnabled) => _sharedPreferences.setBool(PreferencesKey.mwebEnabled, mwebEnabled));
@@ -937,6 +943,9 @@ abstract class SettingsStoreBase with Store {
@observable
bool mwebCardDisplay;
@observable
bool showZcashMissingFundsCard;
@observable
bool mwebEnabled;
@@ -1197,6 +1206,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
final mwebAlwaysScan = sharedPreferences.getBool(PreferencesKey.mwebAlwaysScan) ?? false;
final mwebCardDisplay = sharedPreferences.getBool(PreferencesKey.mwebCardDisplay) ?? true;
final showZcashMissingFundsCard = sharedPreferences.getBool(PreferencesKey.showZcashMissingFundsCard) ?? true;
final mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false;
final hasEnabledMwebBefore =
sharedPreferences.getBool(PreferencesKey.hasEnabledMwebBefore) ?? false;
@@ -1554,6 +1564,7 @@ abstract class SettingsStoreBase with Store {
silentPaymentsCardDisplay: silentPaymentsCardDisplay,
mwebAlwaysScan: mwebAlwaysScan,
mwebCardDisplay: mwebCardDisplay,
showZcashMissingFundsCard: showZcashMissingFundsCard,
mwebEnabled: mwebEnabled,
mwebNodeUri: mwebNodeUri,
hasEnabledMwebBefore: hasEnabledMwebBefore,
@@ -1768,6 +1779,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
mwebAlwaysScan = sharedPreferences.getBool(PreferencesKey.mwebAlwaysScan) ?? false;
mwebCardDisplay = sharedPreferences.getBool(PreferencesKey.mwebCardDisplay) ?? true;
showZcashMissingFundsCard = sharedPreferences.getBool(PreferencesKey.showZcashMissingFundsCard) ?? true;
mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false;
hasEnabledMwebBefore = sharedPreferences.getBool(PreferencesKey.hasEnabledMwebBefore) ?? false;
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
@@ -19,6 +19,7 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/store/dashboard/order_filter_store.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cake_wallet/utils/tor.dart';
import 'package:cake_wallet/wownero/wownero.dart' as wow;
@@ -580,6 +581,13 @@ abstract class DashboardViewModelBase with Store {
];
return errors;
}
@computed
bool get showZcashMissingFundsCard {
if (wallet.type != WalletType.zcash) return false;
if (!settingsStore.showZcashMissingFundsCard) return false;
return zcash!.showMissingFundsCard(wallet);
}
@computed
bool get hasSilentPayments =>
@@ -852,6 +860,16 @@ abstract class DashboardViewModelBase with Store {
mwebEnabled = false;
bitcoin!.setMwebEnabled(wallet, false);
}
@action
Future<void> rescanInternalChangeZcash() async {
await zcash!.rescanInternalChange(wallet);
}
@action
void dismissZcash() {
settingsStore.showZcashMissingFundsCard = false;
}
@action
void dismissDecredInfoCard() {
@@ -105,7 +105,7 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
case WalletType.polygon:
return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
throw Exception('Unexpected wallet type: ${wallet.type} for bitbox');
}
}
}
@@ -189,7 +189,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
case WalletType.polygon:
return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
throw Exception('Unexpected wallet type: ${wallet.type} for ledger');
}
}
@@ -73,7 +73,7 @@ abstract class TrezorViewModelBase extends HardwareWalletViewModel with Store {
case WalletType.polygon:
return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
throw Exception('Unexpected wallet type: ${wallet.type} for trezor');
}
}
}
@@ -53,7 +53,7 @@ abstract class MoneroAccountListViewModelBase with Store {
.toList();
}
throw Exception('Unexpected wallet type: ${_wallet.type}');
throw Exception('Unexpected wallet type: ${_wallet.type} for monero');
}
final WalletBase _wallet;
@@ -36,7 +36,7 @@ abstract class NanoAccountListViewModelBase with Store {
.toList();
}
throw Exception('Unexpected wallet type: ${_wallet.type}');
throw Exception('Unexpected wallet type: ${_wallet.type} for nano');
}
final WalletBase _wallet;
@@ -63,7 +63,7 @@ abstract class PowNodeListViewModelBase with Store {
node = getNanoDefaultPowNode(nodes: _nodeSource)!;
break;
default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type} for pow node list');
}
await setAsCurrent(node);
+1 -1
View File
@@ -1035,7 +1035,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
// priority: priority,
);
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
throw Exception('Unexpected wallet type: ${wallet.type} for send');
}
}
@@ -49,6 +49,9 @@ abstract class DisplaySettingsViewModelBase with Store {
@computed
bool get showAddressBookPopup => _settingsStore.showAddressBookPopupEnabled;
@computed
bool get showZcashCard => _settingsStore.showZcashMissingFundsCard;
@computed
SyncStatusDisplayMode get syncStatusDisplayMode => _settingsStore.syncStatusDisplayMode;
@@ -103,6 +106,9 @@ abstract class DisplaySettingsViewModelBase with Store {
@action
void setBalanceDisplayMode(BalanceDisplayMode value) => _settingsStore.balanceDisplayMode = value;
@action
void setShowZcashCard(bool value) => _settingsStore.showZcashMissingFundsCard = value;
@action
void setShouldDisplayBalance(bool value) {
if (value) {
+22 -7
View File
@@ -5,6 +5,7 @@ import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.d
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_base.dart';
@@ -139,13 +140,15 @@ abstract class WalletKeysViewModelBase with Store {
case WalletType.zano:
keys = zano!.getKeys(_wallet);
break;
case WalletType.zcash:
keys = zcash!.getKeys(_wallet);
break;
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.arbitrum:
case WalletType.bsc:
case WalletType.solana:
case WalletType.zcash:
case WalletType.tron:
items.addAll([
if (_wallet.privateKey != null)
@@ -208,35 +211,47 @@ abstract class WalletKeysViewModelBase with Store {
if (keys != null) {
items.addAll([
if (keys['primaryAddress'] != null)
if ((keys['primaryAddress']??'').isNotEmpty)
StandartListItem(
key: ValueKey('${_walletName}_wallet_primary_address_item_key'),
title: S.current.primary_address,
value: keys['primaryAddress']!),
if (keys['publicSpendKey'] != null)
if ((keys['publicSpendKey']??'').isNotEmpty)
StandartListItem(
key: ValueKey('${_walletName}_wallet_public_spend_key_item_key'),
title: S.current.spend_key_public,
value: keys['publicSpendKey']!,
),
if (keys['privateSpendKey'] != null)
if ((keys['privateSpendKey']??'').isNotEmpty)
StandartListItem(
key: ValueKey('${_walletName}_wallet_private_spend_key_item_key'),
title: S.current.spend_key_private,
value: keys['privateSpendKey']!,
),
if (keys['publicViewKey'] != null)
if ((keys['publicViewKey']??'').isNotEmpty)
StandartListItem(
key: ValueKey('${_walletName}_wallet_public_view_key_item_key'),
title: S.current.view_key_public,
value: keys['publicViewKey']!,
),
if (keys['privateViewKey'] != null)
if ((keys['privateViewKey']??'').isNotEmpty)
StandartListItem(
key: ValueKey('${_walletName}_wallet_private_view_key_item_key'),
title: S.current.view_key_private,
value: keys['privateViewKey']!,
),
if ((keys['tsk']??'').isNotEmpty)
StandartListItem(
key: ValueKey('${_walletName}_wallet_transparent_secret_key_item_key'),
title: S.current.transparent_secret_key,
value: keys['tsk']!,
),
if ((keys['uvk']??'').isNotEmpty)
StandartListItem(
key: ValueKey('${_walletName}_wallet_unified_view_key_item_key'),
title: S.current.unified_view_key,
value: keys['uvk']!,
),
]);
}
}
@@ -292,7 +307,7 @@ abstract class WalletKeysViewModelBase with Store {
case WalletType.zcash:
return 'zcash-wallet';
default:
throw Exception('Unexpected wallet type: ${_wallet.type.toString()}');
throw Exception('Unexpected wallet type: ${_wallet.type.toString()} for wallet keys');
}
}
+10 -2
View File
@@ -57,11 +57,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
case WalletType.decred:
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.zcash:
availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
break;
case WalletType.bitcoinCash:
case WalletType.zano:
case WalletType.zcash:
case WalletType.dogecoin:
availableModes = [WalletRestoreMode.seed];
break;
@@ -95,7 +95,8 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
WalletType.nano,
WalletType.banano,
WalletType.solana,
WalletType.tron
WalletType.tron,
WalletType.zcash,
].contains(type);
late final bool onlyViewKeyRestore =
@@ -307,6 +308,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
password: password,
pubkey: viewKey!,
);
case WalletType.zcash:
return zcash!.createZcashRestoreWalletFromPrivateKey(
name: name,
privateKey: options['private_key'] as String,
password: password,
height: height
);
default:
break;
}
+21 -4
View File
@@ -20,8 +20,14 @@ class CWZcash extends Zcash {
WalletCredentials createZcashRestoreWalletFromPrivateKey(
{required String name,
required String privateKey,
required String password}) {
throw UnimplementedError();
required String password,
required int height}) {
return ZcashFromKeysWalletCredentials(
name: name,
height: height,
privateKey: privateKey,
password: password,
);
}
@override
@@ -122,11 +128,10 @@ class CWZcash extends Zcash {
Map<String, String> getKeys(Object wallet) {
final zcashWallet = wallet as ZcashWallet;
final seed = zcashWallet.seed;
final address = zcashWallet.walletAddresses.address;
return <String, String>{
if (seed != null) 'seed': seed,
'primaryAddress': address,
...(zcashWallet.keys as Map<String, String?>).map((final k, final v) => MapEntry(k, v??'')),
};
}
@@ -209,6 +214,18 @@ class CWZcash extends Zcash {
Future<int> getHeightByDate(DateTime date) {
return ZcashWalletBase.getHeightByDate(date);
}
@override
bool showMissingFundsCard(WalletBase wallet) {
final zcashWallet = wallet as ZcashWallet;
return zcashWallet.couldBeZashiWallet();
}
@override
Future<void> rescanInternalChange(WalletBase wallet) {
final zcashWallet = wallet as ZcashWallet;
return zcashWallet.rescanInternalChange();
}
}
const wordList = [