Files
hash-wallet/lib/view_model/send/output.dart
T
ef9b0fbae6 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>
2026-01-16 03:16:41 +01:00

399 lines
12 KiB
Dart

import 'dart:math';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
import 'package:cake_wallet/entities/contact_base.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
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';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
part 'output.g.dart';
const String cryptoNumberPattern = '0.0';
class Output = OutputBase with _$Output;
abstract class OutputBase with Store {
OutputBase(
this._wallet, this._settingsStore, this._fiatConversationStore, this.cryptoCurrencyHandler)
: key = UniqueKey(),
sendAll = false,
cryptoAmount = '',
cryptoFullBalance = '',
fiatAmount = '',
address = '',
note = '',
extractedAddress = '',
estimatedFee = '0.0',
parsedAddress = ParsedAddress(addresses: []) {
autorun((_) {
final status = _wallet.syncStatus;
printV("Sync status changed to $status. Recalculating fees");
calculateEstimatedFee();
});
}
Key key;
@observable
String fiatAmount;
@observable
String cryptoAmount;
@observable
String cryptoFullBalance;
@observable
String address;
@observable
String note;
@observable
bool sendAll;
@observable
ParsedAddress parsedAddress;
@observable
String extractedAddress;
String? memo;
@computed
bool get isParsedAddress =>
parsedAddress.parseFrom != ParseFrom.notParsed && parsedAddress.name.isNotEmpty;
@observable
String? stealthAddress;
@computed
int get formattedCryptoAmount {
int amount = 0;
try {
if (cryptoAmount.isNotEmpty) {
final _cryptoAmount = cryptoAmount.replaceAll(',', '.');
int _amount = 0;
switch (walletType) {
case WalletType.monero:
_amount = monero!.formatterMoneroParseAmount(amount: _cryptoAmount);
break;
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
case WalletType.dogecoin:
_amount = bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
break;
case WalletType.decred:
_amount = decred!.formatterStringDoubleToDecredAmount(_cryptoAmount);
break;
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.arbitrum:
_amount = evm!.formatterEVMParseAmount(_cryptoAmount);
break;
case WalletType.wownero:
_amount = wownero!.formatterWowneroParseAmount(amount: _cryptoAmount);
break;
case WalletType.zano:
_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:
case WalletType.banano:
case WalletType.solana:
case WalletType.tron:
break;
}
if (_amount > 0) {
amount = _amount;
}
}
} catch (e) {
amount = 0;
}
return amount;
}
@observable
String estimatedFee;
@action
Future<void> calculateEstimatedFee() async {
try {
final priority = _settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId);
if (isEVMCompatibleChain(_wallet.type)) {
await _wallet.updateEstimatedFeesParams(priority);
}
int fee = 0;
if (_settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId) != null) {
fee = _wallet.calculateEstimatedFee(
_settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId)!,
formattedCryptoAmount,
);
}
switch (_wallet.type) {
case WalletType.monero:
estimatedFee = monero!.formatterMoneroAmountToDouble(amount: fee).toString();
break;
case WalletType.bitcoin:
if (_settingsStore.getPriority(_wallet.type) ==
bitcoin!.getBitcoinTransactionPriorityCustom()) {
fee = bitcoin!.getEstimatedFeeWithFeeRate(
_wallet, _settingsStore.customBitcoinFeeRate, formattedCryptoAmount);
}
estimatedFee = bitcoin!.formatterBitcoinAmountToDouble(amount: fee).toString();
break;
case WalletType.litecoin:
case WalletType.bitcoinCash:
case WalletType.dogecoin:
estimatedFee = bitcoin!.formatterBitcoinAmountToDouble(amount: fee).toString();
break;
case WalletType.solana:
estimatedFee = solana!.getEstimateFees(_wallet).toString();
break;
case WalletType.wownero:
estimatedFee = wownero!.formatterWowneroAmountToDouble(amount: fee).toString();
break;
case WalletType.zano:
estimatedFee = zano!
.formatterIntAmountToDouble(
amount: fee, currency: cryptoCurrencyHandler(), forFee: true)
.toString();
break;
case WalletType.decred:
estimatedFee = decred!.formatterDecredAmountToDouble(amount: fee).toString();
break;
case WalletType.tron:
if (cryptoCurrencyHandler() == CryptoCurrency.trx) {
estimatedFee = tron!.getTronNativeEstimatedFee(_wallet).toString();
} else {
estimatedFee = tron!.getTronTRC20EstimatedFee(_wallet).toString();
}
break;
case WalletType.zcash:
estimatedFee = zcash!.formatterZcashAmountToDouble(amount: BigInt.from(fee)).toString();
break;
/// EVMs
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.arbitrum:
final isNative = cryptoCurrencyHandler() == CryptoCurrency.eth ||
cryptoCurrencyHandler() == CryptoCurrency.maticpoly ||
cryptoCurrencyHandler() == CryptoCurrency.baseEth ||
cryptoCurrencyHandler() == CryptoCurrency.arbEth;
String? fee = isNative
? evm!.getEVMNativeEstimatedFee(_wallet)
: evm!.getEVMERC20EstimatedFee(_wallet);
estimatedFee = formatFixed(BigInt.parse(fee ?? '0.0'), 18, fractionalDigits: 12);
break;
/// end EVMs
case WalletType.haven:
case WalletType.nano:
case WalletType.banano:
case WalletType.none:
// will not reach here as it doesn't have priority and this function is triggered only when priority changes
break;
}
} catch (e) {
printV(e.toString());
}
}
@computed
String get estimatedFeeFiatAmount {
// forces mobx to rebuild the computed value
final _ = _wallet.syncStatus;
try {
final currency = (isEVMCompatibleChain(_wallet.type) ||
_wallet.type == WalletType.solana ||
_wallet.type == WalletType.tron)
? _wallet.currency
: cryptoCurrencyHandler();
final fiat = calculateFiatAmountRaw(
price: _fiatConversationStore.prices[currency]!,
cryptoAmount: double.parse(estimatedFee));
return fiat;
} catch (_) {
return '0.00';
}
}
WalletType get walletType => _wallet.type;
final CryptoCurrency Function() cryptoCurrencyHandler;
@observable
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
final SettingsStore _settingsStore;
final FiatConversionStore _fiatConversationStore;
@action
void setSendAll(String fullBalance) {
cryptoFullBalance = fullBalance;
sendAll = true;
_updateFiatAmount();
}
@action
void updateWallet(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> newWallet) {
_wallet = newWallet;
}
@action
void reset() {
sendAll = false;
cryptoAmount = '';
fiatAmount = '';
address = '';
note = '';
memo = null;
resetParsedAddress();
}
void resetParsedAddress() {
extractedAddress = '';
parsedAddress = ParsedAddress(addresses: []);
}
@action
void setCryptoAmount(String amount) {
if (amount.toUpperCase() != S.current.all) {
sendAll = false;
}
cryptoAmount = amount;
_updateFiatAmount();
}
@action
void setFiatAmount(String amount) {
fiatAmount = amount;
_updateCryptoAmount();
}
@action
void _updateFiatAmount() {
try {
final fiat = calculateFiatAmount(
price: _fiatConversationStore.prices[cryptoCurrencyHandler()]!,
cryptoAmount:
sendAll ? cryptoFullBalance.replaceAll(",", ".") : cryptoAmount.replaceAll(',', '.'));
if (fiatAmount != fiat) {
fiatAmount = fiat;
}
} catch (_) {
fiatAmount = '';
}
}
@action
void _updateCryptoAmount() {
try {
final decimals = min(20, cryptoCurrencyHandler().decimals);
final crypto = (double.parse(fiatAmount.replaceAll(',', '.')) /
_fiatConversationStore.prices[cryptoCurrencyHandler()]!)
.toStringAsFixed(decimals);
if (cryptoAmount != crypto) {
cryptoAmount = crypto;
}
} catch (e) {
cryptoAmount = '';
}
}
Map<String, dynamic> get extra {
final fields = <String, dynamic>{};
if (parsedAddress.parseFrom == ParseFrom.bip353) {
fields['bip353_name'] = parsedAddress.name;
fields['bip353_proof'] = parsedAddress.bip353DnsProof;
}
return fields;
}
Future<void> fetchParsedAddress(BuildContext context) async {
final domain = address;
final currency = cryptoCurrencyHandler();
parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
extractedAddress = await extractAddressFromParsed(context, parsedAddress);
note = parsedAddress.description;
}
void loadContact(ContactBase contact) {
address = contact.name;
parsedAddress = ParsedAddress.fetchContactAddress(address: contact.address, name: contact.name);
extractedAddress = parsedAddress.addresses.first;
note = parsedAddress.description;
}
}
extension OutputCopyWith on Output {
Output OutputCopyWithParsedAddress({
ParsedAddress? parsedAddress,
String? fiatAmount,
}) {
final clone = Output(
_wallet,
_settingsStore,
_fiatConversationStore,
cryptoCurrencyHandler,
);
clone
..cryptoAmount = cryptoAmount
..cryptoFullBalance = cryptoFullBalance
..note = note
..sendAll = sendAll
..memo = memo
..stealthAddress = stealthAddress
..parsedAddress = parsedAddress ?? this.parsedAddress
..fiatAmount = fiatAmount ?? this.fiatAmount;
return clone;
}
}