v5.8.0 Release Candidate (#2815)

* v5.8.0 Release Candidate

Add Arbitrum
EVM chains enhancements
Integrate Jupiter swaps for Solana
Bug fixes

* Exclude transparent address from unified address

* fix hiddenAddresses getting discarded [skip ci]

* pump zcash order [skip ci]

* CW-1372: zcash improvements and missing features second iteration (#2818)

* fix: remove hidden addresses from usable address list
fix: getBlockHeightByTime fallback to offline calculation
fix: remove NewWalletTypeViewModel to fix groups working without restart

* fix: rotate T addresses in exchanges

* fix: builds without --zcash config (linux)

* fix: update address after trade

* Add zcash.me

* printv [skip ci]

---------

Co-authored-by: cyan <cyjan@mrcyjanek.net>
This commit is contained in:
Omar Hatem
2026-01-20 19:23:30 +01:00
committed by GitHub
co-authored by cyan
parent 02225fab5d
commit 9ca2346a45
19 changed files with 185 additions and 70 deletions
+4 -2
View File
@@ -1,3 +1,5 @@
Add Zcash wallet
Generic enhancements
Add Arbitrum
Zcash improvements
EVM chains enhancements
Integrate Jupiter swaps for Solana
Bug fixes
+25 -5
View File
@@ -17,6 +17,7 @@ import 'dart:io';
import 'package:cw_core/pathForWallet.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:cw_zcash/src/util/crc32.dart';
import 'package:cw_zcash/src/zcash_wallet.dart';
@@ -232,6 +233,21 @@ class ZcashTaddressRotation {
final txs = WarpApi.getTxsSync(coin, a.id);
return txs.isNotEmpty;
});
// remove hidden addresses
final wis = await WalletInfo.selectList('type = ?', [WalletType.zcash.index]);
final List<String> hiddenAddresses_ = [];
for (int k = 0; k < wis.length; k++) {
final addrs = await wis[i].getHiddenAddresses();
final addr2 = await wis[i].getUsedAddresses();
hiddenAddresses_.addAll(addrs);
hiddenAddresses_.addAll(addr2);
}
final Set<String> hiddenAddresses = hiddenAddresses_.toSet();
rotationAccountsUsable[raKeys[i]]!.removeWhere((final a) {
return hiddenAddresses.contains(WarpApi.getTAddr(coin, a.id));
});
}
printV("rotationAccountsUsable: ${rotationAccountsUsable.length}");
@@ -376,12 +392,16 @@ class ZcashTaddressRotation {
}
}
static String? addressForAccount(final int accountId) {
final acc = rotationAccountsUsable[accountId]?.firstOrNull;
if (acc == null) {
return null;
static String? addressForAccount(final int accountId, final Set<String>? hiddenAddresses) {
final accs = rotationAccountsUsable[accountId] ?? [];
for (int i = 0; i < accs.length; i++) {
final addr = WarpApi.getTAddr(coin, accs[i].id);
if (hiddenAddresses?.contains(addr) == true) {
continue;
}
return addr;
}
return WarpApi.getTAddr(coin, acc.id);
return null;
}
static List<String>? allAddressesForAccount(final int accountId) {
@@ -75,7 +75,6 @@ class ZcashTransactionInfo extends TransactionInfo {
static final Map<String, String> _destinationAddressMap = {};
static String? getCachedDestinationAddress(final String txId) {
printV("$txId -> ${_destinationAddressMap.keys.join(",")}");
return _destinationAddressMap[txId] ??
_destinationAddressMap['"$txId"'] ??
_destinationAddressMap[txId.replaceAll('"', '')];
+52 -3
View File
@@ -783,7 +783,7 @@ abstract class ZcashWalletBase
final newWalletCredentials = credentials as ZcashNewWalletCredentials;
String mnemonic;
if (newWalletCredentials.mnemonic != null && newWalletCredentials.mnemonic!.isNotEmpty) {
if (newWalletCredentials.mnemonic?.isNotEmpty == true) {
mnemonic = newWalletCredentials.mnemonic!;
} else {
final strength = (newWalletCredentials.seedPhraseLength == 24) ? 256 : 128;
@@ -970,7 +970,56 @@ abstract class ZcashWalletBase
_initialized = true;
}
static Future<int> getHeightByDate(final DateTime date) {
return WarpApi.getBlockHeightByTime(coin, date);
static Future<int> getBlockHeightByTime(final DateTime time) async {
final genesisTime = DateTime.utc(2016, 10, 28);
const genesisHeight = 0;
final firstHalvingTime = DateTime.utc(2020, 11, 18);
const firstHalvingHeight = 1046400;
final secondHalvingTime = DateTime.utc(2024, 11, 23);
const secondHalvingHeight = 2726400;
final t = time.toUtc().millisecondsSinceEpoch;
final t0 = genesisTime.millisecondsSinceEpoch;
final t1 = firstHalvingTime.millisecondsSinceEpoch;
final t2 = secondHalvingTime.millisecondsSinceEpoch;
if (t <= t0) return genesisHeight;
if (t < t1) {
return _interpolate(genesisHeight, firstHalvingHeight, t0, t1, t);
}
if (t < t2) {
return _interpolate(firstHalvingHeight, secondHalvingHeight, t1, t2, t);
}
final secondsSince2 = (t - t2) / 1000.0;
final blocksSince2 = (secondsSince2 / 76.0).floor();
return secondHalvingHeight + blocksSince2;
}
static int _interpolate(
final int hStart,
final int hEnd,
final int tStart,
final int tEnd,
final int t,
) {
if (tEnd == tStart) return hStart;
final ratio = (t - tStart) / (tEnd - tStart);
return (hStart + (hEnd - hStart) * ratio).round();
}
static Future<int> getHeightByDate(final DateTime date) async {
int height = await getBlockHeightByTime(date);
try {
final h2 = await WarpApi.getBlockHeightByTime(coin, date);
height = h2;
} catch (e) {
printV("getHeightByDate: $e");
}
return height;
}
}
+19 -12
View File
@@ -51,7 +51,7 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
return orchardAddress;
case ZcashAddressType.unifiedType:
if (_unifiedAddress == null) {
_unifiedAddress = WarpApi.getAddress(ZcashWalletBase.coin, accountId, 7);
_unifiedAddress = WarpApi.getAddress(ZcashWalletBase.coin, accountId, 6);
}
return _unifiedAddress ?? "";
}
@@ -91,7 +91,7 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
@computed
String? get transparentAddressRotated {
try {
return ZcashTaddressRotation.addressForAccount(accountId);
return ZcashTaddressRotation.addressForAccount(accountId, hiddenAddresses);
} catch (e) {
return null;
}
@@ -120,6 +120,7 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
}
@override
@computed
String get addressForExchange => transparentAddressRotated ?? transparentAddress;
@override
@@ -130,17 +131,23 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
static int get coin => ZcashWalletBase.coin;
@override
Future<void> init() async {
@observable
Set<String> hiddenAddresses = {};
Future<void> _initAddresses() async {
addressesMap = await walletInfo.getAddresses();
addressInfos = await walletInfo.getAddressInfos();
usedAddresses = await walletInfo.getUsedAddresses();
manualAddresses = await walletInfo.getManualAddresses();
hiddenAddresses = await walletInfo.getHiddenAddresses();
address = latestAddress;
}
addressesMap = {"a": "b"};
@override
Future<void> init() async {
await _initAddresses();
await ZcashTaddressRotation.init();
printV(ZcashTaddressRotation.rotationAccounts.keys);
int accountIndex = 0;
addressInfos = {
0:
@@ -155,11 +162,13 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
}).toList() ??
[],
};
hiddenAddresses = ZcashTaddressRotation.allUsedAddressesForAccount(accountId)?.toSet() ?? {};
hiddenAddresses.addAll(
ZcashTaddressRotation.allUsedAddressesForAccount(accountId)?.toSet() ?? {},
);
addressInfos[0]?.removeWhere((final test) => hiddenAddresses.contains(test.address));
// addressInfos[0]?.removeWhere((final test) => hiddenAddresses.contains(test.address));
if (_addressPageType == ZcashAddressType.transparentRotated) {
final addr = ZcashTaddressRotation.addressForAccount(accountId);
final addr = ZcashTaddressRotation.addressForAccount(accountId, hiddenAddresses);
if (addr != null) {
address = addr;
}
@@ -167,14 +176,11 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
await saveAddressesInBox();
}
@override
@observable
late String _address = latestAddress;
@computed
String get address {
if (addressPageType == ZcashAddressType.transparentRotated) {
return transparentAddressRotated ?? transparentAddress;
}
return _address;
}
@@ -191,6 +197,7 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
await walletInfo.setUsedAddresses(usedAddresses.toList());
await walletInfo.setHiddenAddresses(hiddenAddresses.toList());
await walletInfo.setManualAddresses(manualAddresses.toList());
await _initAddresses();
} catch (e) {
printV("Error saving addresses: $e");
}
+6
View File
@@ -84,6 +84,8 @@ PODS:
- payjoin_flutter (0.23.0)
- permission_handler_apple (9.3.0):
- Flutter
- quick_actions_ios (0.0.1):
- Flutter
- reown_yttrium (0.0.1):
- Flutter
- YttriumWrapper (= 0.8.35)
@@ -169,6 +171,7 @@ DEPENDENCIES:
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- payjoin_flutter (from `.symlinks/plugins/payjoin_flutter/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- quick_actions_ios (from `.symlinks/plugins/quick_actions_ios/ios`)
- reown_yttrium (from `.symlinks/plugins/reown_yttrium/ios`)
- sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
@@ -243,6 +246,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/payjoin_flutter/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
quick_actions_ios:
:path: ".symlinks/plugins/quick_actions_ios/ios"
reown_yttrium:
:path: ".symlinks/plugins/reown_yttrium/ios"
sensitive_clipboard:
@@ -299,6 +304,7 @@ SPEC CHECKSUMS:
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
payjoin_flutter: d9d4c8aa16bd5dfedb9b21d0edc8199e0187d96e
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
quick_actions_ios: 56f3cbaa71e94f212838d1f9fe354bd0734779bf
reown_yttrium: c0e87e5965fa60a3559564cc35cffbba22976089
SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
+2 -6
View File
@@ -188,7 +188,6 @@ import 'package:cake_wallet/view_model/cake_pay/cake_pay_account_view_model.dart
import 'package:cake_wallet/view_model/cake_pay/cake_pay_cards_list_view_model.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
import 'package:cake_wallet/view_model/new_wallet_type_view_model.dart';
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
import 'package:cake_wallet/view_model/wallet_groups_display_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
@@ -442,10 +441,6 @@ Future<void> setup({
newWalletArguments: newWalletArgs,
));
final walletList = await WalletInfo.getAll();
getIt.registerFactory<NewWalletTypeViewModel>(() => NewWalletTypeViewModel(walletList.isNotEmpty));
getIt.registerFactory<WalletManager>(
() => WalletManager(
getIt.get<SharedPreferences>(),
@@ -579,6 +574,8 @@ Future<void> setup({
sharedPreferences: getIt.get<SharedPreferences>(),
keyService: getIt.get<KeyService>()));
final walletList = await WalletInfo.getAll();
getIt.registerFactory<AuthService>(
() => AuthService(
secureStorage: getIt.get<SecureStorage>(),
@@ -1326,7 +1323,6 @@ Future<void> setup({
(newWalletTypeArguments, _) {
return NewWalletTypePage(
newWalletTypeArguments: newWalletTypeArguments,
newWalletTypeViewModel: getIt.get<NewWalletTypeViewModel>(),
);
});
@@ -15,6 +15,7 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/twitter/twitter_api.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
@@ -216,6 +217,19 @@ class AddressResolver {
Future<ParsedAddress> resolve(BuildContext context, String text, CryptoCurrency currency) async {
final ticker = currency.title;
try {
if (text.startsWith("zcash.me")) {
final parts = text.split("/");
final handle = parts.last;
if (parts.length == 2 && handle.isNotEmpty) {
final extractZcashAddress = await _fetchZcashAddress(handle);
if (extractZcashAddress != null) {
return ParsedAddress.zcashAddress(
address: extractZcashAddress,
name: handle,
);
}
}
}
// twitter handle example: @username
if (text.startsWith('@') && !text.substring(1).contains('@')) {
if (currency == CryptoCurrency.zano && settingsStore.lookupsZanoAlias) {
@@ -479,4 +493,28 @@ class AddressResolver {
return ParsedAddress(addresses: [text]);
}
Future<String?> _fetchZcashAddress(String handle) async {
final url = Uri.parse('https://zcash.me/$handle');
try {
final response = await ProxyWrapper().get(clearnetUri: url);
if (response.statusCode == 200) {
final addressRegex = RegExp(
r'(t1[0-9A-Za-z]{33}|t3[0-9A-Za-z]{33}|zs[a-z0-9]{76}|u1[a-z0-9]{1,300})',
caseSensitive: true,
);
final match = addressRegex.firstMatch(response.body);
if (match != null) {
return match.group(0);
}
}
} catch (e) {
printV('Error fetching zcash.me profile: $e');
}
return null;
}
}
+9
View File
@@ -15,6 +15,7 @@ enum ParseFrom {
thorChain,
wellKnown,
zanoAlias,
zcashAddress,
bip353
}
@@ -167,6 +168,14 @@ class ParsedAddress {
);
}
factory ParsedAddress.zcashAddress({required String address, required String name}) {
return ParsedAddress(
addresses: [address],
name: name,
parseFrom: ParseFrom.zcashAddress,
);
}
factory ParsedAddress.fetchWellKnownAddress({required String address, required String name}) {
return ParsedAddress(
addresses: [address],
@@ -13,7 +13,6 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/new_wallet_type_view_model.dart';
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/hardware/device_connection_type.dart';
@@ -23,11 +22,9 @@ import 'package:flutter/material.dart';
class NewWalletTypePage extends BasePage {
NewWalletTypePage({
required this.newWalletTypeViewModel,
required this.newWalletTypeArguments,
});
final NewWalletTypeViewModel newWalletTypeViewModel;
final NewWalletTypeArguments newWalletTypeArguments;
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
@@ -50,7 +47,6 @@ class NewWalletTypePage extends BasePage {
Widget body(BuildContext context) => WalletTypeForm(
walletImage: currentTheme.isDark ? walletTypeImage : walletTypeLightImage,
isCreate: newWalletTypeArguments.isCreate,
newWalletTypeViewModel: newWalletTypeViewModel,
onTypeSelected: newWalletTypeArguments.onTypeSelected,
hardwareWalletType: newWalletTypeArguments.hardwareWalletType,
);
@@ -60,14 +56,12 @@ class WalletTypeForm extends StatefulWidget {
WalletTypeForm({
required this.walletImage,
required this.isCreate,
required this.newWalletTypeViewModel,
this.onTypeSelected,
this.hardwareWalletType,
});
final bool isCreate;
final Image walletImage;
final NewWalletTypeViewModel newWalletTypeViewModel;
final void Function(BuildContext, WalletType)? onTypeSelected;
final HardwareWalletType? hardwareWalletType;
@@ -195,9 +189,10 @@ class WalletTypeFormState extends State<WalletTypeForm> {
// If it's a restore flow, trigger the external callback
// If it's not a BIP39 Wallet or if there are no other wallets, route to the newWallet page
// Any other scenario, route to pre-existing seed page
final walletList = await WalletInfo.getAll();
if (!widget.isCreate) {
widget.onTypeSelected!(context, selected!);
} else if (!isBIP39Wallet(selected!) || !widget.newWalletTypeViewModel.hasExisitingWallet) {
} else if (!isBIP39Wallet(selected!) || walletList.isEmpty) {
Navigator.of(context).pushNamed(
Routes.newWallet,
arguments: NewWalletArguments(type: selected!),
@@ -73,6 +73,11 @@ Future<String> extractAddressFromParsed(
content = S.of(context).extracted_address_content('${parsedAddress.name} (Zano Alias)');
address = parsedAddress.addresses.first;
break;
case ParseFrom.zcashAddress:
title = S.of(context).address_detected;
content = S.of(context).extracted_address_content('${parsedAddress.name} (Zcash.me)');
address = parsedAddress.addresses.first;
break;
case ParseFrom.bip353:
title = S.of(context).address_detected;
content = S.of(context).extracted_address_content('${parsedAddress.name} (BIP-353)');
@@ -1,11 +0,0 @@
import 'package:mobx/mobx.dart';
part 'new_wallet_type_view_model.g.dart';
class NewWalletTypeViewModel = NewWalletTypeViewModelBase with _$NewWalletTypeViewModel;
abstract class NewWalletTypeViewModelBase with Store {
NewWalletTypeViewModelBase(this.hasExisitingWallet);
final bool hasExisitingWallet;
}
+2 -2
View File
@@ -176,7 +176,7 @@ class CWZcash extends Zcash {
}
@override
ZcashAddressType getZcashAddressType(ReceivePageOption option) {
dynamic getZcashAddressType(ReceivePageOption option) {
switch (option) {
case ZcashReceivePageOption.unified:
return ZcashAddressType.unifiedType;
@@ -196,7 +196,7 @@ class CWZcash extends Zcash {
}
@override
ZcashAddressType getOptionToType(ReceivePageOption option) {
dynamic getOptionToType(ReceivePageOption option) {
return (option as ZcashReceivePageOption).toType();
}
+4 -4
View File
@@ -14,15 +14,15 @@ TYPES=($MONERO_COM $CAKEWALLET)
APP_ANDROID_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="5.6.5"
MONERO_COM_BUILD_NUMBER=4150
MONERO_COM_VERSION="5.8.0"
MONERO_COM_BUILD_NUMBER=4151
MONERO_COM_BUNDLE_ID="com.monero.app"
MONERO_COM_PACKAGE="com.monero.app"
MONERO_COM_SCHEME="monero.com"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="5.7.0"
CAKEWALLET_BUILD_NUMBER=4298
CAKEWALLET_VERSION="5.8.0"
CAKEWALLET_BUILD_NUMBER=4299
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
CAKEWALLET_SCHEME="cakewallet"
+4 -4
View File
@@ -12,13 +12,13 @@ TYPES=($MONERO_COM $CAKEWALLET)
APP_IOS_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="5.6.5"
MONERO_COM_BUILD_NUMBER=153
MONERO_COM_VERSION="5.8.0"
MONERO_COM_BUILD_NUMBER=155
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="5.7.0"
CAKEWALLET_BUILD_NUMBER=363
CAKEWALLET_VERSION="5.8.0"
CAKEWALLET_BUILD_NUMBER=366
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
+2 -2
View File
@@ -14,8 +14,8 @@ if [ -n "$1" ]; then
fi
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="5.6.1"
CAKEWALLET_BUILD_NUMBER=75
CAKEWALLET_VERSION="5.8.0"
CAKEWALLET_BUILD_NUMBER=76
if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then
echo "Wrong app type."
+1 -1
View File
@@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in
$MONERO_COM)
CONFIG_ARGS="--monero";;
$CAKEWALLET)
CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --zcash --arbitrum";;
CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --arbitrum";;
esac
cp -rf pubspec_description.yaml pubspec.yaml
+4 -4
View File
@@ -16,13 +16,13 @@ if [ -n "$1" ]; then
fi
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="5.6.6"
MONERO_COM_BUILD_NUMBER=74
MONERO_COM_VERSION="5.8.0"
MONERO_COM_BUILD_NUMBER=75
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="5.7.0"
CAKEWALLET_BUILD_NUMBER=142
CAKEWALLET_VERSION="5.8.0"
CAKEWALLET_BUILD_NUMBER=143
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then
+6 -6
View File
@@ -1631,9 +1631,9 @@ abstract class Zcash {
List<TransactionPriority> getTransactionPriorities();
List<ReceivePageOption> getZcashReceivePageOptions(Object wallet);
ReceivePageOption getSelectedAddressType(Object wallet);
ZcashAddressType getZcashAddressType(ReceivePageOption option);
dynamic getZcashAddressType(ReceivePageOption option);
Future<void> setAddressType(Object wallet, dynamic option);
ZcashAddressType getOptionToType(ReceivePageOption option);
dynamic getOptionToType(ReceivePageOption option);
void unlockDatabase(String password);
Future<int> getHeightByDate(DateTime date);
}
@@ -1862,6 +1862,10 @@ Future<void> generateWalletTypes({
outputContent += '\tWalletType.solana,\n';
}
if (hasZcash) {
outputContent += '\tWalletType.zcash,\n';
}
if (hasTron) {
outputContent += '\tWalletType.tron,\n';
}
@@ -1874,10 +1878,6 @@ Future<void> generateWalletTypes({
outputContent += '\tWalletType.bitcoinCash,\n';
}
if (hasZcash) {
outputContent += '\tWalletType.zcash,\n';
}
if (hasBitcoin) {
outputContent += '\tWalletType.litecoin,\n';
}