zec ironwood (#3425)

* initial

* wip

* ironwood detection, migration

* dev: zec address validation on rt

* fix: ironwood spend

* ironwood regressions

* fix: tx history

* bump: zkool2

* fix: tx amounts on pending
fix: ironwood pending balance
fix: orchard->ironwood migration
fix: orchard->any pre ironwood txs
fix: ironwood -> any txs
fix: WrongSpendAuthorizingKey
fix: tx history
fix: autoshield to ironwood
fix: zkool to latest version
break: my sleep schedule :sweating: :worksonmymachine:

* thx fable

* downgrade frb to 2.11.1

* fix: tx amounts in history

* Migating... / migration label for O->I txs
This commit is contained in:
cyan
2026-07-27 10:20:40 +02:00
committed by GitHub
parent b73c46ac59
commit 92c2c5e043
38 changed files with 1064 additions and 203 deletions
+11 -4
View File
@@ -3,6 +3,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/zcash/zcash_network_type.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
@@ -10,8 +11,11 @@ const BEFORE_REGEX = '(^|\\s)';
const AFTER_REGEX = '(\$|\\s)';
class AddressValidator extends TextValidator {
AddressValidator({required CryptoCurrency type, bool isTestnet = false})
: super(
AddressValidator({
required CryptoCurrency type,
bool isTestnet = false,
String? network,
}) : super(
errorMessage: S.current.error_text_address,
useAdditionalValidation: [CryptoCurrency.btc, CryptoCurrency.ltc].contains(type)
? (String txt) {
@@ -32,7 +36,7 @@ class AddressValidator extends TextValidator {
: type == CryptoCurrency.zano
? zano?.validateAddress
: null,
pattern: getPattern(type, isTestnet: isTestnet),
pattern: getPattern(type, isTestnet: isTestnet, network: network),
length: getLength(type),
);
@@ -61,7 +65,7 @@ class AddressValidator extends TextValidator {
CryptoCurrency.btcln,
];
static String getPattern(CryptoCurrency type, {bool isTestnet = false}) {
static String? getPattern(CryptoCurrency type, {bool isTestnet = false, String? network}) {
var pattern = "";
if (type is Erc20Token) {
pattern = '0x[0-9a-zA-Z]+';
@@ -170,6 +174,9 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.hbar:
pattern = '[0-9a-zA-Z.]+';
case CryptoCurrency.zec:
if (ZcashNetworkType.isDevNetwork(network)) {
return null;
}
pattern = '(?:'
't1[0-9A-Za-z]{33}'
'|t3[0-9A-Za-z]{33}'
@@ -6,6 +6,7 @@ import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
@@ -34,6 +35,7 @@ class _ReceiveAddressTypeSelectorState extends State<ReceiveAddressTypeSelector>
@override
Widget build(BuildContext context) {
return Observer(builder: (final _) {
final commonOptions = widget.receiveOptionViewModel.options
.where((element) =>
element.isCommon ||
@@ -223,6 +225,7 @@ class _ReceiveAddressTypeSelectorState extends State<ReceiveAddressTypeSelector>
],
)),
);
});
}
}
+6
View File
@@ -162,6 +162,7 @@ import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/zcash/zcash_network_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -822,6 +823,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
final isChildWallet = args['isChildWallet'] as bool? ?? false;
final useTestnet = args['useTestnet'] as bool;
final toggleTestnet = args['toggleTestnet'] as Function(bool? val);
final zcashNetwork = args['zcashNetwork'] as int? ?? ZcashNetworkType.mainnet;
final setZcashNetwork =
args['setZcashNetwork'] as void Function(int network)? ?? (_) {};
final restoredWallet = args['restoredWallet'] as RestoredWallet?;
final viewModelParam = {'type': type, 'isPow': false};
@@ -832,6 +836,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
isChildWallet: isChildWallet,
useTestnet: useTestnet,
toggleUseTestnet: toggleTestnet,
zcashNetwork: zcashNetwork,
setZcashNetwork: setZcashNetwork,
advancedPrivacySettingsViewModel:
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
nodeViewModel: getIt.get<NodeCreateOrEditViewModel>(param1: viewModelParam),
@@ -14,11 +14,13 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/zcash/zcash_network_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -28,6 +30,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
required this.isChildWallet,
required this.useTestnet,
required this.toggleUseTestnet,
required this.zcashNetwork,
required this.setZcashNetwork,
required this.advancedPrivacySettingsViewModel,
required this.nodeViewModel,
required this.seedSettingsViewModel,
@@ -44,6 +48,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
final bool isChildWallet;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
final int zcashNetwork;
final void Function(int network) setZcashNetwork;
@override
Widget body(BuildContext context) => _AdvancedPrivacySettingsBody(
@@ -51,6 +57,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
isChildWallet,
useTestnet,
toggleUseTestnet,
zcashNetwork,
setZcashNetwork,
advancedPrivacySettingsViewModel,
nodeViewModel,
seedSettingsViewModel,
@@ -63,6 +71,8 @@ class _AdvancedPrivacySettingsBody extends StatefulWidget {
this.isChildWallet,
this.useTestnet,
this.toggleUseTestnet,
this.zcashNetwork,
this.setZcashNetwork,
this.privacySettingsViewModel,
this.nodeViewModel,
this.seedTypeViewModel,
@@ -76,6 +86,8 @@ class _AdvancedPrivacySettingsBody extends StatefulWidget {
final bool isChildWallet;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
final int zcashNetwork;
final void Function(int network) setZcashNetwork;
@override
_AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState();
@@ -87,6 +99,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
final _formKey = GlobalKey<NodeFormState>();
final _passphraseFormKey = GlobalKey<FormState>();
bool? testnetValue;
int? zcashNetworkValue;
bool obscurePassphrase = true;
@@ -112,6 +125,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
if (testnetValue == null && widget.useTestnet) {
testnetValue = widget.useTestnet;
}
zcashNetworkValue ??= widget.zcashNetwork;
return Container(
padding: EdgeInsets.only(top: 24),
@@ -281,6 +295,20 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
],
);
}),
if (FeatureFlag.hasDevOptions &&
widget.privacySettingsViewModel.type == WalletType.zcash)
SettingsChoicesCell(
ChoicesListItem<int>(
title: 'Zcash network',
items: ZcashNetworkType.values,
selectedItem: zcashNetworkValue ?? ZcashNetworkType.mainnet,
displayItem: ZcashNetworkType.label,
onItemSelected: (final network) {
setState(() => zcashNetworkValue = network);
widget.setZcashNetwork(network);
},
),
),
if (widget.privacySettingsViewModel.type == WalletType.bitcoin ||
widget.privacySettingsViewModel.type == WalletType.decred)
Builder(builder: (_) {
@@ -316,6 +316,8 @@ class _WalletNameFormState extends State<WalletNameForm> {
"type": _walletNewVM.type,
"useTestnet": _walletNewVM.useTestnet,
"toggleTestnet": _walletNewVM.toggleUseTestnet,
"zcashNetwork": _walletNewVM.zcashNetwork,
"setZcashNetwork": _walletNewVM.setZcashNetwork,
"isChildWallet": widget.isChildWallet,
});
},
@@ -121,7 +121,9 @@ class WalletRestorePage extends BasePage {
'isFromRestore': true,
'type': walletRestoreViewModel.type,
'useTestnet': walletRestoreViewModel.useTestnet,
'toggleTestnet': walletRestoreViewModel.toggleUseTestnet
'toggleTestnet': walletRestoreViewModel.toggleUseTestnet,
'zcashNetwork': walletRestoreViewModel.zcashNetwork,
'setZcashNetwork': walletRestoreViewModel.setZcashNetwork,
},
);
},
@@ -538,10 +538,16 @@ abstract class DashboardViewModelBase with Store {
.map((item) => _txIdentityString(item.transaction.txHash, item.transaction.direction))
.toSet();
transactions.removeWhere(
(item) => newKeys
.contains(_txIdentityString(item.transaction.txHash, item.transaction.direction)),
);
transactions.removeWhere((item) {
if (wallet.type == WalletType.zcash) {
return newTransactions.any(
(n) => n.transaction.txHash == item.transaction.txHash,
);
}
return newKeys.contains(
_txIdentityString(item.transaction.txHash, item.transaction.direction),
);
});
transactions.addAll(newTransactions);
// transactions.clear();
@@ -3,6 +3,7 @@ import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_zcash/cw_zcash.dart';
import 'package:mobx/mobx.dart';
part 'receive_option_view_model.g.dart';
@@ -18,7 +19,14 @@ abstract class ReceiveOptionViewModelBase with Store {
? ReceivePageOption.testnet
: _wallet.type == WalletType.zcash
? zcash!.getSelectedAddressType(_wallet)
: ReceivePageOption.mainnet);
: ReceivePageOption.mainnet) {
if (_wallet.type == WalletType.zcash) {
reaction<bool>(
(_) => (_wallet.walletAddresses as ZcashWalletAddresses).ironwoodActive,
(_) => _syncZcashShieldedLabel(),
);
}
}
final WalletBase _wallet;
@@ -27,10 +35,21 @@ abstract class ReceiveOptionViewModelBase with Store {
@observable
ReceivePageOption selectedReceiveOption;
@computed
List<ReceivePageOption> get options => _wallet.walletAddresses.receivePageOptions;
String get walletTypeString => walletTypeToString(_wallet.type);
@action
void selectReceiveOption(ReceivePageOption option) => selectedReceiveOption = option;
@action
void _syncZcashShieldedLabel() {
for (final option in options) {
if (option.value == selectedReceiveOption.value) {
selectedReceiveOption = option;
return;
}
}
}
}
@@ -60,6 +60,9 @@ class TransactionListItem extends ActionListItem with Keyable {
return 'Transaction has missing data';
}
if (transaction.additionalInfo['isIronwoodMigration'] == true) {
return 'Migration';
}
if (transaction.additionalInfo['isAutoShield'] == true) {
if (transaction.isPending) {
final status = formattedStatus;
@@ -48,8 +48,11 @@ abstract class SendTemplateViewModelBase with Store {
amountParsingProxy: _appStore.amountParsingProxy,
);
AddressValidator get addressValidator =>
AddressValidator(type: _wallet.currency, isTestnet: _wallet.isTestnet);
AddressValidator get addressValidator => AddressValidator(
type: _wallet.currency,
isTestnet: _wallet.isTestnet,
network: _wallet.walletInfo.network,
);
TemplateValidator get templateValidator => TemplateValidator();
+9 -3
View File
@@ -190,7 +190,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
// Check if the address is valid for the current currency (except for Zano, which can use handles as addresses)
if (selectedCryptoCurrency != CryptoCurrency.zano) {
final isValidAddress = AddressValidator(type: selectedCryptoCurrency).isValid(query);
final isValidAddress = AddressValidator(
type: selectedCryptoCurrency,
network: wallet.walletInfo.network,
).isValid(query);
if (isValidAddress) return null;
}
@@ -317,8 +320,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
Validator<String> get allAmountValidator => AllAmountValidator();
Validator<String> get addressValidator =>
AddressValidator(type: selectedCryptoCurrency, isTestnet: wallet.isTestnet);
Validator<String> get addressValidator => AddressValidator(
type: selectedCryptoCurrency,
isTestnet: wallet.isTestnet,
network: wallet.walletInfo.network,
);
Validator<String> get textValidator => TextValidator();
@@ -428,6 +428,9 @@ abstract class TransactionDetailsViewModelBase with Store {
}
String get formattedTitle {
if (transactionInfo.additionalInfo['isIronwoodMigration'] == true) {
return 'Migration';
}
if (transactionInfo.additionalInfo['isAutoShield'] == true) {
return S.current.shielding;
}
+9
View File
@@ -17,6 +17,7 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/zcash/zcash_network_type.dart';
import 'package:mobx/mobx.dart';
import 'package:polyseed/polyseed.dart';
@@ -33,9 +34,13 @@ abstract class WalletCreationVMBase with Store {
@observable
bool _useTestnet = false;
int _zcashNetwork = ZcashNetworkType.mainnet;
@computed
bool get useTestnet => _useTestnet;
int get zcashNetwork => _zcashNetwork;
@observable
String name;
@@ -252,4 +257,8 @@ abstract class WalletCreationVMBase with Store {
void toggleUseTestnet(bool? value) {
_useTestnet = value ?? !_useTestnet;
}
void setZcashNetwork(int network) {
_zcashNetwork = network;
}
}
+1
View File
@@ -146,6 +146,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
password: walletPassword,
mnemonic: newWalletArguments!.mnemonic,
passphrase: passphrase,
network: zcashNetwork,
);
case WalletType.decred:
return decred!.createDecredNewWalletCredentials(name: name);
@@ -224,6 +224,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
password: password,
passphrase: passphrase,
height: height,
network: zcashNetwork,
);
case WalletType.none:
case WalletType.haven:
+13 -18
View File
@@ -7,12 +7,14 @@ class CWZcash extends Zcash {
WalletInfo? walletInfo,
String? password,
String? mnemonic,
required String? passphrase}) {
required String? passphrase,
int network = 0}) {
return ZcashNewWalletCredentials(
name: name,
passphrase: passphrase,
password: password,
mnemonic: mnemonic,
network: network,
);
}
@@ -36,9 +38,10 @@ class CWZcash extends Zcash {
required String mnemonic,
required String password,
String? passphrase,
required int? height}) {
required int? height,
int network = 0}) {
return ZcashFromSeedWalletCredentials(
name: name, seed: mnemonic, passphrase: passphrase, password: password, height: height);
name: name, seed: mnemonic, passphrase: passphrase, password: password, height: height, network: network);
}
@override
@@ -161,9 +164,12 @@ class CWZcash extends Zcash {
@override
ReceivePageOption getSelectedAddressType(Object wallet) {
final zcashWallet = wallet as ZcashWallet;
final t =
(zcashWallet.walletAddresses as ZcashWalletAddresses).walletInfo.addressPageType ?? "";
return ZcashReceivePageOption.fromType(ZcashReceivePageOption.typeFromString(t));
final addresses = zcashWallet.walletAddresses as ZcashWalletAddresses;
final type = ZcashReceivePageOption.typeFromString(addresses.walletInfo.addressPageType ?? "");
if (type == ZcashAddressType.shieldedOrchard) {
return ZcashReceivePageOption.shieldedOrchard(ironwood: addresses.ironwoodActive);
}
return ZcashReceivePageOption.fromType(type);
}
bool hasSelectedTransparentAddress(Object wallet) {
@@ -172,18 +178,7 @@ class CWZcash extends Zcash {
@override
dynamic getZcashAddressType(ReceivePageOption option) {
switch (option) {
case ZcashReceivePageOption.unified:
return ZcashAddressType.unifiedType;
case ZcashReceivePageOption.transparent:
return ZcashAddressType.transparent;
case ZcashReceivePageOption.shieldedSapling:
return ZcashAddressType.shieldedSapling;
case ZcashReceivePageOption.shieldedOrchard:
return ZcashAddressType.shieldedOrchard;
default:
throw Exception("Unknown ReceivePageOption!");
}
return (option as ZcashReceivePageOption).toType();
}
@override
+17
View File
@@ -0,0 +1,17 @@
/// Zcash network indices passed from lib/ into cw_zcash.
abstract final class ZcashNetworkType {
static const int mainnet = 0;
static const int testnet = 1;
static const int regtest = 2;
static const List<int> values = [mainnet, testnet, regtest];
static String label(final int network) => switch (network) {
testnet => 'Testnet',
regtest => 'Regtest',
_ => 'Mainnet',
};
static bool isDevNetwork(final String? network) =>
network?.toLowerCase() == 'testnet' || network?.toLowerCase() == 'regtest';
}