Ywallet zcash (#2810)

* feat(zcash): initial commit
- initial work on cw_zcash
  - use single .db for everything zcash to speed up sync and scanning
  - implement silent autoshielding of non-orchard funds
  - use FFI for everything to fetch the data
  - add support for passphrase by adding extra word to seed phrase
  - enforce lints on CI for cw_zcash directory
- use correct zcash name, and call transparent zcash tZEC
- minor cleanups outsite of cw_zcash
  - gitmodules, gitignore
  - db debug
  - fix broken addressPageType in WalletInfo
  - enable debug options in kDebugMode and kProfileMode

* feat: T address rotation (and cache) wip: minor fixes

* wip: shield tx list

* fix fiat amounts fix derrivation of rotation account addresses screen
for zcash block height fix T address shields improvements Amount in
disposable T shields UI improvements
show pending outgoing tx in tx history

* fix null check on passphrase page fix tx showing as pending rename auto
shield txs fixed send all to include fee use rotating address in
addressForExchange wrap all calls of WarpApi.getLatestHeight in try
catch to prevent leaking errors

other minor fixes from notion

* wallet group debug cache sent tx address wallet T address rotation fixes
automatically pick the fee for auto-shield

* fix T address cache deserialization fix autoshield icon fix autoshield
text getCachedDestinationAddress fix send address cache

* address comments from review

* wip: iOS fix

* fix swap

* Allow cakepay payment from zcash wallet

* fix: ios (I'm stupid) fix: reorder addresses to make orchard default
fix: send all balance

* 5.6.6 bump fix: ios apple review add transport error to ignored
exceptions

* fix: leaving dust behind when sweeping all

* Change wallets order

* fix: reflect balance of pending out txs fix: restore height support

* linux support (disabled)

* fix: date picker for zcash on restore screen fix: build script for linux
match WarpApi.warpSync args with upstream

* fix: linter script

* ensure all exchange providers support ZEC (#2802)

* ensure all exchange providers support ZEC

* remove zaddr and tzec currencies and refactor providers

* Update lib/exchange/provider/near_Intents_exchange_provider.dart

* add back old zcash currencies for backward compatibility, thanks Czarek

* minor fix [skip ci]

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* Update android/app/src/main/AndroidManifestBase.xml [skip ci]

* Update ios/Runner/InfoBase.plist [skip ci]

* fix: remove unused code fix: macos fix: disable on linux and macos

* update build numbers [skip ci]

* Add zcash to birdpay

---------

Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
Co-authored-by: Serhii <borodenko.sv@gmail.com>
This commit is contained in:
Omar Hatem
2026-01-16 03:16:41 +01:00
committed by GitHub
co-authored by Czarek Nakamoto Serhii
parent 2a77d359f1
commit ef9b0fbae6
141 changed files with 5664 additions and 284 deletions
@@ -57,6 +57,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
case WalletType.arbitrum:
case WalletType.solana:
case WalletType.tron:
case WalletType.zcash:
return true;
case WalletType.bitcoin:
@@ -103,6 +104,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
WalletType.wownero,
WalletType.zano,
WalletType.dogecoin,
WalletType.zcash,
].contains(type);
@computed
@@ -115,6 +115,8 @@ abstract class CakePayBuyCardViewModelBase with Store {
];
case WalletType.monero:
return [CakePayPaymentMethod.XMR];
case WalletType.zcash:
return [CakePayPaymentMethod.ZEC];
default:
return const [];
}
@@ -139,11 +141,14 @@ abstract class CakePayBuyCardViewModelBase with Store {
@action
Future<void> createOrder() async {
if (walletType != WalletType.bitcoin &&
walletType != WalletType.monero &&
walletType != WalletType.litecoin) {
if (![
WalletType.bitcoin,
WalletType.monero,
WalletType.litecoin,
WalletType.zcash,
].contains(walletType)) {
sendViewModel.state =
FailureState('Unsupported wallet type, please use Bitcoin, Monero, or Litecoin.');
FailureState('Unsupported wallet type, please use Bitcoin, Monero, Litecoin or Zcash.');
}
try {
order = await _cakePayService.createOrder(
@@ -320,6 +320,7 @@ abstract class BalanceViewModelBase with Store {
case WalletType.wownero:
case WalletType.zano:
case WalletType.decred:
case WalletType.zcash:
return true;
default:
return false;
@@ -957,6 +957,7 @@ abstract class DashboardViewModelBase with Store {
return true;
case WalletType.zano:
case WalletType.haven:
case WalletType.zcash:
case WalletType.none:
return false;
}
@@ -1284,6 +1285,7 @@ abstract class DashboardViewModelBase with Store {
if (wallet.chainId == 1 && tx.evmSignatureName == 'approval')
return ' (${tx.evmSignatureName})';
return '';
}
@@ -231,6 +231,7 @@ abstract class HomeSettingsViewModelBase with Store {
case WalletType.bitcoinCash:
case WalletType.decred:
case WalletType.dogecoin:
case WalletType.zcash:
return false;
}
@@ -1,4 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
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';
@@ -16,7 +17,9 @@ abstract class ReceiveOptionViewModelBase with Store {
? bitcoin!.getSelectedAddressType(_wallet)
: (_wallet.type == WalletType.decred && _wallet.isTestnet)
? ReceivePageOption.testnet
: ReceivePageOption.mainnet),
: _wallet.type == WalletType.zcash
? zcash!.getSelectedAddressType(_wallet)
: ReceivePageOption.mainnet),
_options = [] {
final walletType = _wallet.type;
switch (walletType) {
@@ -46,6 +49,12 @@ abstract class ReceiveOptionViewModelBase with Store {
_options = ReceivePageOptions;
}
break;
case WalletType.zcash:
_options = [
...zcash!.getZcashReceivePageOptions(_wallet),
...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
];
break;
default:
_options = ReceivePageOptions;
}
@@ -9,6 +9,7 @@ import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.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/crypto_currency.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
@@ -52,6 +53,9 @@ class TransactionListItem extends ActionListItem with Keyable {
}
String get formattedTitle {
if (transaction.additionalInfo['autoShield'] == true) {
return "Autoshield";
}
if (transaction.direction == TransactionDirection.incoming) {
return S.current.received;
}
@@ -217,6 +221,11 @@ class TransactionListItem extends ActionListItem with Keyable {
cryptoAmount: decred!.formatterDecredAmountToDouble(amount: transaction.amount),
price: price);
break;
case WalletType.zcash:
amount = calculateFiatAmountRaw(
cryptoAmount: zcash!.formatterZcashAmountToDouble(amount: BigInt.from(transaction.amount)),
price: price);
case WalletType.none:
case WalletType.banano:
case WalletType.haven:
@@ -922,6 +922,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.dcr;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.zcash:
depositCurrency = CryptoCurrency.zec;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.none:
break;
}
@@ -97,6 +97,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
case WalletType.dogecoin:
case WalletType.zano:
case WalletType.decred:
case WalletType.zcash:
return false;
}
}
+5
View File
@@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
@@ -102,6 +103,7 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.solana:
case WalletType.tron:
case WalletType.arbitrum:
case WalletType.zcash:
return false;
}
}
@@ -185,6 +187,9 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.zano:
_settingsStore.setPriority(wallet.type, monero!.getMoneroTransactionPriorityAutomatic());
break;
case WalletType.zcash:
_settingsStore.setPriority[wallet.type] = zcash!.getZcashTransactionPriorityAutomatic();
break;
case WalletType.bitcoin:
_settingsStore.setPriority(wallet.type, bitcoin!.getBitcoinTransactionPriorityMedium());
break;
+8
View File
@@ -18,6 +18,7 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/tron/tron.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/balance.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/format_fixed.dart';
@@ -125,6 +126,9 @@ abstract class OutputBase with Store {
_amount = zano!
.formatterParseAmount(amount: _cryptoAmount, currency: cryptoCurrencyHandler());
break;
case WalletType.zcash:
_amount = zcash!.formatterZcashParseAmount(_cryptoAmount);
break;
case WalletType.none:
case WalletType.haven:
case WalletType.nano:
@@ -204,6 +208,10 @@ abstract class OutputBase with Store {
estimatedFee = tron!.getTronTRC20EstimatedFee(_wallet).toString();
}
break;
case WalletType.zcash:
estimatedFee = zcash!.formatterZcashAmountToDouble(amount: BigInt.from(fee)).toString();
break;
/// EVMs
case WalletType.ethereum:
+9 -2
View File
@@ -45,6 +45,7 @@ import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.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/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/exceptions.dart';
@@ -803,8 +804,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
wallet.type != WalletType.banano &&
wallet.type != WalletType.solana &&
wallet.type != WalletType.tron &&
wallet.type != WalletType.arbitrum) {
// Wallet type is generic for all EVM chains, so we need to check the chainId
wallet.type != WalletType.arbitrum &&
wallet.type != WalletType.zcash) {
throw Exception('Priority is null for wallet type: ${wallet.type}');
}
@@ -861,6 +862,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.decred:
this.coinTypeToSpendFrom = UnspentCoinType.any;
return decred!.createDecredTransactionCredentials(outputs, priority!);
case WalletType.zcash:
return zcash!.createZcashTransactionCredentials(
outputs,
currency: selectedCryptoCurrency,
// priority: priority,
);
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
}
@@ -1,5 +1,6 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/package_info.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
@@ -95,6 +95,8 @@ abstract class TransactionDetailsViewModelBase with Store {
case WalletType.dogecoin:
_addDogecoinListItems(tx, dateFormat);
break;
case WalletType.zcash:
_addZcashListItems(tx, dateFormat);
case WalletType.none:
case WalletType.banano:
break;
@@ -211,7 +213,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}';
case WalletType.dogecoin:
return 'https://blockchair.com/dogecoin/transaction/${txId}';
case WalletType.zcash:
return 'https://blockchair.com/zcash/transaction/${txId}';
case WalletType.none:
return '';
}
@@ -257,6 +260,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'basescan.org';
case WalletType.arbitrum:
return S.current.view_transaction_on + 'arbiscan.io';
case WalletType.zcash:
return S.current.view_transaction_on + 'blockchair.com';
case WalletType.none:
return '';
}
@@ -755,6 +760,57 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items);
}
void _addZcashListItems(TransactionInfo tx, DateFormat dateFormat) {
final memo = tx.additionalInfo['memo'] as String?;
final _items = [
StandartListItem(
title: S.current.transaction_details_transaction_id,
value: tx.txHash,
key: ValueKey('standard_list_item_transaction_details_id_key'),
),
if (tx.to != null)
StandartListItem(
title: S.current.transaction_details_recipient_address,
value: tx.to!,
key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date),
key: ValueKey('standard_list_item_transaction_details_date_key'),
),
StandartListItem(
title: S.current.confirmations,
value: tx.confirmations.toString(),
key: ValueKey('standard_list_item_transaction_confirmations_key'),
),
StandartListItem(
title: S.current.transaction_details_height,
value: '${tx.height ?? 0}',
key: ValueKey('standard_list_item_transaction_details_height_key'),
),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted(),
key: ValueKey('standard_list_item_transaction_details_amount_key'),
),
if (tx.feeFormatted() != null && tx.feeFormatted()!.isNotEmpty)
StandartListItem(
title: S.current.transaction_details_fee,
value: tx.feeFormatted()!,
key: ValueKey('standard_list_item_transaction_details_fee_key'),
),
if (memo != null && memo.isNotEmpty)
StandartListItem(
title: S.current.memo,
value: memo,
key: ValueKey('standard_list_item_transaction_details_memo_key'),
),
];
items.addAll(_items);
}
void _addDogecoinListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(
@@ -28,6 +28,7 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/amount_converter.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
@@ -165,6 +166,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return BaseURI(amount: amount, address: address.address);
case WalletType.arbitrum:
return ArbitrumURI(amount: amount, address: address.address);
case WalletType.zcash:
return ZcashURI(amount: amount, address: address.address);
case WalletType.none:
throw Exception('Unexpected type: ${type.toString()}');
}
@@ -313,6 +316,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
});
}
if (wallet.type == WalletType.zcash) {
final addrInfos = zcash!.getAddressInfos(wallet);
addrInfos.forEach((info) {
addressList.add(
new WalletAddressListItem(isPrimary: false, address: info.address, name: info.label));
});
}
for (var i = 0; i < addressList.length; i++) {
if (!(addressList[i] is WalletAddressListItem)) continue;
(addressList[i] as WalletAddressListItem).isHidden = wallet.walletAddresses.hiddenAddresses
@@ -409,6 +420,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
WalletType.litecoin,
WalletType.decred,
WalletType.dogecoin,
WalletType.zcash,
].contains(wallet.type);
@computed
@@ -528,6 +540,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type)) {
await bitcoin!.setAddressType(wallet, option);
}
if (wallet.type == WalletType.zcash) {
await zcash!.setAddressType(wallet, option);
}
}
void _init() {
@@ -144,6 +144,7 @@ abstract class WalletKeysViewModelBase with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.solana:
case WalletType.zcash:
case WalletType.tron:
items.addAll([
if (_wallet.privateKey != null)
@@ -285,6 +286,8 @@ abstract class WalletKeysViewModelBase with Store {
return 'decred-wallet';
case WalletType.dogecoin:
return 'dogecoin-wallet';
case WalletType.zcash:
return 'zcash-wallet';
default:
throw Exception('Unexpected wallet type: ${_wallet.type.toString()}');
}
+7
View File
@@ -6,6 +6,7 @@ import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zcash/zcash.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
@@ -138,6 +139,12 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
password: walletPassword,
passphrase: passphrase,
);
case WalletType.zcash:
return zcash!.createZcashNewWalletCredentials(
name: name,
password: walletPassword,
passphrase: passphrase,
);
case WalletType.decred:
return decred!.createDecredNewWalletCredentials(name: name);
case WalletType.none:
+19 -4
View File
@@ -19,6 +19,7 @@ import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_creation_vm.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/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
@@ -59,10 +60,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
break;
case WalletType.bitcoinCash:
case WalletType.zano:
case WalletType.none:
case WalletType.zcash:
case WalletType.dogecoin:
availableModes = [WalletRestoreMode.seed];
break;
case WalletType.none:
availableModes = [];
}
walletCreationService.changeWalletType(type: type);
if (restoredWallet != null) {
@@ -79,9 +82,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
late final bool hasSeedLanguageSelector =
[WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
late final bool hasBlockchainHeightSelector =
[WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
late final bool hasBlockchainHeightSelector = [
WalletType.monero,
WalletType.haven,
WalletType.wownero,
WalletType.zcash
].contains(type);
late final bool hasRestoreFromPrivateKey = [
WalletType.ethereum,
WalletType.polygon,
@@ -202,9 +209,17 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
);
case WalletType.decred:
return decred!.createDecredRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
);
case WalletType.zcash:
return zcash!.createZcashRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
passphrase: passphrase,
height: height,
);
case WalletType.none:
case WalletType.haven:
+1 -1
View File
@@ -84,7 +84,7 @@ abstract class WalletSeedViewModelBase with Store {
bool shouldPerformVerification() {
bool isCI = bool.fromEnvironment('CI_BUILD', defaultValue: false);
bool isDebug = kDebugMode;
bool isDebug = kDebugMode || kProfileMode;
if (isDebug && !isCI) {
printV("Skipping verification in debug mode - $isDebug (and when it's not in CI - $isCI).");