import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/evm_transaction_error_fees_handler.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/wallet_contact.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/src/screens/ur/animated_ur_page.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cw_core/exceptions.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coin_type.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/core/address_validator.dart'; import 'package:cake_wallet/core/amount_validator.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/calculate_fiat_amount.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:collection/collection.dart'; part 'send_view_model.g.dart'; class SendViewModel = SendViewModelBase with _$SendViewModel; abstract class SendViewModelBase extends WalletChangeListenerViewModel with Store { @override void onWalletChange(wallet) { currencies = wallet.balance.keys.toList(); selectedCryptoCurrency = wallet.currency; hasMultipleTokens = isEVMCompatibleChain(wallet.type) || wallet.type == WalletType.solana || wallet.type == WalletType.tron; } UnspentCoinsListViewModel unspentCoinsListViewModel; SendViewModelBase( AppStore appStore, this.sendTemplateViewModel, this._fiatConversationStore, this.balanceViewModel, this.contactListViewModel, this.transactionDescriptionBox, this.ledgerViewModel, this.unspentCoinsListViewModel, { this.coinTypeToSpendFrom = UnspentCoinType.any, }) : state = InitialExecutionState(), currencies = appStore.wallet!.balance.keys.toList(), selectedCryptoCurrency = appStore.wallet!.currency, hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type) || appStore.wallet!.type == WalletType.solana || appStore.wallet!.type == WalletType.tron, outputs = ObservableList(), _settingsStore = appStore.settingsStore, fiatFromSettings = appStore.settingsStore.fiatCurrency, super(appStore: appStore) { if (wallet.type == WalletType.bitcoin && _settingsStore.priority[wallet.type] == bitcoinTransactionPriorityCustom) { setTransactionPriority(bitcoinTransactionPriorityMedium); } final priority = _settingsStore.priority[wallet.type]; final priorities = priorityForWalletType(wallet.type); if (!priorityForWalletType(wallet.type).contains(priority) && priorities.isNotEmpty) { _settingsStore.priority[wallet.type] = priorities.first; } outputs .add(Output(wallet, _settingsStore, _fiatConversationStore, () => selectedCryptoCurrency)); } @observable ExecutionState state; ObservableList outputs; final UnspentCoinType coinTypeToSpendFrom; bool get showAddressBookPopup => _settingsStore.showAddressBookPopupEnabled; @action void addOutput() { outputs .add(Output(wallet, _settingsStore, _fiatConversationStore, () => selectedCryptoCurrency)); } @action void removeOutput(Output output) { if (isBatchSending) { outputs.remove(output); } } @action void clearOutputs() { outputs.clear(); addOutput(); } @computed bool get isBatchSending => outputs.length > 1; bool get shouldDisplaySendALL { if (walletType == WalletType.solana) return false; // if (walletType == WalletType.ethereum && selectedCryptoCurrency == CryptoCurrency.eth) // return false; // if (walletType == WalletType.polygon && selectedCryptoCurrency == CryptoCurrency.maticpoly) // return false; return true; } @computed String get pendingTransactionFiatAmount { if (pendingTransaction == null) { return '0.00'; } try { final fiat = calculateFiatAmount( price: _fiatConversationStore.prices[selectedCryptoCurrency]!, cryptoAmount: pendingTransaction!.amountFormatted); return fiat; } catch (_) { return '0.00'; } } @computed String get pendingTransactionFeeFiatAmount { try { if (pendingTransaction != null) { final currency = pendingTransactionFeeCurrency(walletType); final fiat = calculateFiatAmount( price: _fiatConversationStore.prices[currency]!, cryptoAmount: pendingTransaction!.feeFormatted); return fiat; } else { return '0.00'; } } catch (_) { return '0.00'; } } CryptoCurrency pendingTransactionFeeCurrency(WalletType type) { switch (type) { case WalletType.ethereum: case WalletType.polygon: case WalletType.tron: case WalletType.solana: return wallet.currency; default: return selectedCryptoCurrency; } } FiatCurrency get fiat => _settingsStore.fiatCurrency; TransactionPriority get transactionPriority { final priority = _settingsStore.priority[wallet.type]; if (priority == null) { throw Exception('Unexpected type ${wallet.type}'); } return priority; } int? getCustomPriorityIndex(List priorities) { if (wallet.type == WalletType.bitcoin) { final customItem = priorities .firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom()); return customItem != null ? priorities.indexOf(customItem) : null; } return null; } int? get maxCustomFeeRate { if (wallet.type == WalletType.bitcoin) { return bitcoin!.getMaxCustomFeeRate(wallet); } return null; } @computed int get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate; void set customBitcoinFeeRate(int value) => _settingsStore.customBitcoinFeeRate = value; CryptoCurrency get currency => wallet.currency; Validator get amountValidator => AmountValidator(currency: walletTypeToCryptoCurrency(wallet.type)); Validator get allAmountValidator => AllAmountValidator(); Validator get addressValidator => AddressValidator(type: selectedCryptoCurrency); Validator get textValidator => TextValidator(); final FiatCurrency fiatFromSettings; @observable PendingTransaction? pendingTransaction; @computed String get balance { if (coinTypeToSpendFrom == UnspentCoinType.mweb) { return balanceViewModel.balances.values.first.secondAvailableBalance; } else if (coinTypeToSpendFrom == UnspentCoinType.nonMweb) { return balanceViewModel.balances.values.first.availableBalance; } return wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance; } @computed bool get isFiatDisabled => balanceViewModel.isFiatDisabled; @computed String get pendingTransactionFiatAmountFormatted => isFiatDisabled ? '' : pendingTransactionFiatAmount + ' ' + fiat.title; @computed String get pendingTransactionFeeFiatAmountFormatted => isFiatDisabled ? '' : pendingTransactionFeeFiatAmount + ' ' + fiat.title; @computed bool get isReadyForSend => wallet.syncStatus is SyncedSyncStatus || // If silent payments scanning, can still send payments (wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus); @computed List