mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-29 06:38:45 +01:00
Merge upstream Cake Wallet v6.5.0
Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535 upstream commits, including the Monero silent-open fix, Monero on Trezor, Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9. Merged with both sides normalized first (import rename, 100-column format), which took the conflict count from 614 paths to 322. The procedure is now scripts/upstream_sync.sh. Kept from Hash Bags: - WalletType enum order and Hive IDs. SQLite stores WalletType.index and our order differs from upstream's; taking theirs would reread saved wallets as other chains. Our frozen adapter keeps the shipped IDs. - The 1.0.2 legacy PIN and wallet-password decode and self-heal. - Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out. - Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me), converted to upstream's isDefault node-list key; Wownero is back in the node reset list. - Privacy: address resolvers limited to domain-anchored sources again; prices.neroswap.com for fiat; upstream's charts tab, which reads prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false. - Lightning stays behind FeatureFlag.isLightningEnabled, including upstream's new post-seed Lightning username step. - Our icon pipeline, branding and bundle identifiers. Twelve new upstream widget tests are skipped: test and code are unchanged from v6.5.0 apart from renames (not rerun on a pristine upstream checkout; upstream's top_bar_test does not compile against its own TopBar). flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped; flutter build bundle compiles. Built with Flutter 3.41.9.
This commit is contained in:
@@ -1,25 +1,24 @@
|
||||
import 'dart:math' show min;
|
||||
import 'package:hash_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_sources.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/di.dart';
|
||||
import 'package:hash_wallet/entities/calculate_fiat_amount.dart';
|
||||
import 'package:hash_wallet/entities/calculate_fiat_amount_raw.dart';
|
||||
import 'package:hash_wallet/entities/contact_base.dart';
|
||||
import 'package:hash_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:hash_wallet/entities/parsed_address.dart';
|
||||
import 'package:hash_wallet/evm/evm.dart';
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/monero/monero.dart';
|
||||
import 'package:hash_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:hash_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
|
||||
import 'package:hash_wallet/store/app_store.dart';
|
||||
import 'package:hash_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:hash_wallet/wownero/wownero.dart';
|
||||
import 'package:cw_core/amount/amount_sanitizer.dart';
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/format_fixed.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
@@ -45,12 +44,13 @@ abstract class OutputBase with Store {
|
||||
note = '',
|
||||
memo = "",
|
||||
extractedAddress = '',
|
||||
estimatedFee = '0.0',
|
||||
parsedAddress = ParsedAddress(addresses: []) {
|
||||
estimatedFee = Money.zero(cryptoCurrencyHandler()),
|
||||
parsedAddress = ParsedAddress(parsedAddressByCurrencyMap: {}) {
|
||||
autorun((_) {
|
||||
final status = _wallet.syncStatus;
|
||||
printV("Sync status changed to $status. Recalculating fees");
|
||||
|
||||
if (status is! SyncedSyncStatus) {
|
||||
return;
|
||||
}
|
||||
calculateEstimatedFee();
|
||||
});
|
||||
}
|
||||
@@ -59,6 +59,9 @@ abstract class OutputBase with Store {
|
||||
|
||||
bool get useSatoshi => _appStore.amountParsingProxy.useSatoshi(cryptoCurrencyHandler());
|
||||
|
||||
@observable
|
||||
bool isFiatEntry = false;
|
||||
|
||||
@observable
|
||||
String fiatAmount;
|
||||
|
||||
@@ -69,8 +72,7 @@ abstract class OutputBase with Store {
|
||||
String? displayName;
|
||||
|
||||
@computed
|
||||
String get displayCryptoAmount =>
|
||||
_appStore.amountParsingProxy.getDisplayCryptoAmount(cryptoAmount, cryptoCurrencyHandler());
|
||||
String get displayCryptoAmount => _appStore.amountParsingProxy.asDisplayString(cryptoAmountMoney);
|
||||
|
||||
@observable
|
||||
String cryptoFullBalance;
|
||||
@@ -95,14 +97,13 @@ abstract class OutputBase with Store {
|
||||
|
||||
@computed
|
||||
bool get isParsedAddress =>
|
||||
parsedAddress.parseFrom != ParseFrom.notParsed && parsedAddress.name.isNotEmpty;
|
||||
parsedAddress.addressSource != AddressSource.notParsed && parsedAddress.handle.isNotEmpty;
|
||||
|
||||
String roundedCryptoAmount(int digits) => displayCryptoAmount.withMaxDecimals(digits);
|
||||
|
||||
String roundedFiatAmount(int digits) {
|
||||
if (fiatAmount.split(".").last.length <= digits) {
|
||||
return fiatAmount;
|
||||
}
|
||||
if (fiatAmount.split(".").last.length <= digits) return fiatAmount;
|
||||
|
||||
return double.tryParse(fiatAmount.replaceAll(",", ""))?.toStringAsPrecision(digits) ?? '0.0';
|
||||
}
|
||||
|
||||
@@ -110,52 +111,18 @@ abstract class OutputBase with Store {
|
||||
String? stealthAddress;
|
||||
|
||||
@computed
|
||||
int get formattedCryptoAmount {
|
||||
int amount = 0;
|
||||
Money get cryptoAmountMoney {
|
||||
if (cryptoAmount.isEmpty) return Money.zero(cryptoCurrencyHandler());
|
||||
|
||||
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 = cryptoCurrencyHandler().parseAmount(_cryptoAmount).toInt();
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
case WalletType.base:
|
||||
case WalletType.arbitrum:
|
||||
case WalletType.bsc:
|
||||
_amount = evm!.formatterEVMParseAmount(_cryptoAmount);
|
||||
break;
|
||||
case WalletType.wownero:
|
||||
_amount = wownero!.formatterWowneroParseAmount(amount: _cryptoAmount);
|
||||
break;
|
||||
case WalletType.none:
|
||||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
break;
|
||||
}
|
||||
|
||||
if (_amount > 0) {
|
||||
amount = _amount;
|
||||
}
|
||||
}
|
||||
return cryptoCurrencyHandler().parseAmount(cryptoAmount.sanitized());
|
||||
} catch (e) {
|
||||
amount = 0;
|
||||
return Money.zero(cryptoCurrencyHandler());
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
@observable
|
||||
String estimatedFee;
|
||||
Money estimatedFee;
|
||||
|
||||
@action
|
||||
Future<void> calculateEstimatedFee() async {
|
||||
@@ -169,7 +136,7 @@ abstract class OutputBase with Store {
|
||||
if (_settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId) != null) {
|
||||
fee = _wallet.calculateEstimatedFee(
|
||||
_settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId)!,
|
||||
formattedCryptoAmount,
|
||||
cryptoAmountMoney.amount.toInt(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -179,22 +146,20 @@ abstract class OutputBase with Store {
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoinCash:
|
||||
case WalletType.dogecoin:
|
||||
estimatedFee = walletTypeToCryptoCurrency(_wallet.type).formatAmount(BigInt.from(fee));
|
||||
estimatedFee = Money.fromInt(fee, walletTypeToCryptoCurrency(_wallet.type));
|
||||
break;
|
||||
case WalletType.bitcoin:
|
||||
if (cryptoCurrencyHandler() == CryptoCurrency.btcln) {
|
||||
estimatedFee =
|
||||
_appStore.amountParsingProxy.getDisplayCryptoString(10, cryptoCurrencyHandler());
|
||||
estimatedFee = Money.fromInt(10, cryptoCurrencyHandler());
|
||||
break;
|
||||
}
|
||||
if (_settingsStore.getPriority(_wallet.type) ==
|
||||
bitcoin!.getBitcoinTransactionPriorityCustom()) {
|
||||
fee = bitcoin!.getEstimatedFeeWithFeeRate(
|
||||
_wallet, _settingsStore.customBitcoinFeeRate, formattedCryptoAmount);
|
||||
_wallet, _settingsStore.customBitcoinFeeRate, cryptoAmountMoney.amount.toInt());
|
||||
}
|
||||
|
||||
estimatedFee =
|
||||
_appStore.amountParsingProxy.getDisplayCryptoString(fee, cryptoCurrencyHandler());
|
||||
estimatedFee = Money.fromInt(fee, cryptoCurrencyHandler());
|
||||
break;
|
||||
|
||||
/// EVMs
|
||||
@@ -203,16 +168,20 @@ abstract class OutputBase with Store {
|
||||
case WalletType.base:
|
||||
case WalletType.arbitrum:
|
||||
case WalletType.bsc:
|
||||
final isNative = cryptoCurrencyHandler() == CryptoCurrency.eth ||
|
||||
cryptoCurrencyHandler() == CryptoCurrency.maticpoly ||
|
||||
cryptoCurrencyHandler() == CryptoCurrency.baseEth ||
|
||||
cryptoCurrencyHandler() == CryptoCurrency.arbEth ||
|
||||
cryptoCurrencyHandler() == CryptoCurrency.bnb;
|
||||
String? fee = isNative
|
||||
final isNative = [
|
||||
CryptoCurrency.eth,
|
||||
CryptoCurrency.maticpoly,
|
||||
CryptoCurrency.baseEth,
|
||||
CryptoCurrency.arbEth,
|
||||
CryptoCurrency.bnb,
|
||||
].contains(cryptoCurrencyHandler());
|
||||
|
||||
final fee = isNative
|
||||
? evm!.getEVMNativeEstimatedFee(_wallet)
|
||||
: evm!.getEVMERC20EstimatedFee(_wallet);
|
||||
|
||||
estimatedFee = formatFixed(BigInt.parse(fee ?? '0.0'), 18, fractionalDigits: 12);
|
||||
estimatedFee =
|
||||
Money(BigInt.parse(fee ?? '0.0'), walletTypeToCryptoCurrency(_wallet.type));
|
||||
break;
|
||||
|
||||
/// end EVMs
|
||||
@@ -235,10 +204,11 @@ abstract class OutputBase with Store {
|
||||
|
||||
try {
|
||||
final currency =
|
||||
isEVMCompatibleChain(_wallet.type) ? _wallet.currency : cryptoCurrencyHandler();
|
||||
isEVMCompatibleChain(_wallet.type) || cryptoCurrencyHandler() == CryptoCurrency.btcln
|
||||
? _wallet.currency
|
||||
: cryptoCurrencyHandler();
|
||||
|
||||
final cryptoAmount = double.parse(
|
||||
_appStore.amountParsingProxy.getCanonicalCryptoAmount(estimatedFee, currency));
|
||||
final cryptoAmount = double.parse(estimatedFee.toString());
|
||||
|
||||
return calculateFiatAmountRaw(
|
||||
price: _fiatConversationStore.prices[currency]!, cryptoAmount: cryptoAmount);
|
||||
@@ -252,7 +222,7 @@ abstract class OutputBase with Store {
|
||||
|
||||
WalletType get walletType => _wallet.type;
|
||||
|
||||
final CryptoCurrency Function() cryptoCurrencyHandler;
|
||||
final CryptoCurrency Function([CryptoCurrency?]) cryptoCurrencyHandler;
|
||||
final FiatConversionStore _fiatConversationStore;
|
||||
final AppStore _appStore;
|
||||
|
||||
@@ -270,6 +240,7 @@ abstract class OutputBase with Store {
|
||||
void updateWallet(
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> newWallet) {
|
||||
_wallet = newWallet;
|
||||
estimatedFee = Money.zero(cryptoCurrencyHandler());
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -283,10 +254,22 @@ abstract class OutputBase with Store {
|
||||
resetParsedAddress();
|
||||
}
|
||||
|
||||
@action
|
||||
void resetParsedAddress() {
|
||||
displayName = null;
|
||||
extractedAddress = '';
|
||||
parsedAddress = ParsedAddress(addresses: []);
|
||||
note = '';
|
||||
parsedAddress = ParsedAddress(parsedAddressByCurrencyMap: {});
|
||||
}
|
||||
|
||||
@action
|
||||
void applyAddressLookupResult(ParsedAddress result) {
|
||||
final currency = cryptoCurrencyHandler();
|
||||
|
||||
parsedAddress = result;
|
||||
extractedAddress = result.parsedAddressByCurrencyMap[currency] ?? '';
|
||||
note = result.description;
|
||||
displayName = result.profileName.isNotEmpty ? result.profileName : result.handle;
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -332,10 +315,21 @@ abstract class OutputBase with Store {
|
||||
? CryptoCurrency.btc
|
||||
: cryptoCurrencyHandler();
|
||||
|
||||
final price = _fiatConversationStore.prices[cryptoCurrency];
|
||||
if (price == null || !price.isFinite || price <= 0) {
|
||||
printV('_updateCryptoAmount: invalid fiat rate $price for $cryptoCurrency');
|
||||
cryptoAmount = '';
|
||||
return;
|
||||
}
|
||||
|
||||
final decimals = min(20, cryptoCurrencyHandler().decimals);
|
||||
final crypto = (double.parse(fiatAmount.replaceAll(',', '.')) /
|
||||
_fiatConversationStore.prices[cryptoCurrency]!)
|
||||
.toStringAsFixed(decimals);
|
||||
final rawCrypto = double.parse(fiatAmount.replaceAll(',', '.')) / price;
|
||||
if (!rawCrypto.isFinite) {
|
||||
cryptoAmount = '';
|
||||
return;
|
||||
}
|
||||
|
||||
final crypto = rawCrypto.toStringAsFixed(decimals);
|
||||
|
||||
if (cryptoAmount != crypto) cryptoAmount = crypto;
|
||||
} catch (e) {
|
||||
@@ -346,29 +340,26 @@ abstract class OutputBase with Store {
|
||||
|
||||
Map<String, dynamic> get extra {
|
||||
final fields = <String, dynamic>{};
|
||||
if (parsedAddress.parseFrom == ParseFrom.bip353) {
|
||||
fields['bip353_name'] = parsedAddress.name;
|
||||
if (parsedAddress.addressSource == AddressSource.bip353) {
|
||||
fields['bip353_name'] = parsedAddress.handle;
|
||||
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);
|
||||
if (parsedAddress.name.isNotEmpty) {
|
||||
displayName = parsedAddress.name;
|
||||
}
|
||||
extractedAddress = await extractAddressFromParsed(context, parsedAddress);
|
||||
note = parsedAddress.description;
|
||||
}
|
||||
|
||||
@action
|
||||
void loadContact(ContactBase contact) {
|
||||
address = contact.name;
|
||||
parsedAddress = ParsedAddress.fetchContactAddress(address: contact.address, name: contact.name);
|
||||
extractedAddress = parsedAddress.addresses.first;
|
||||
note = parsedAddress.description;
|
||||
final currency = cryptoCurrencyHandler();
|
||||
|
||||
address = contact.address;
|
||||
applyAddressLookupResult(
|
||||
ParsedAddress(
|
||||
parsedAddressByCurrencyMap: {currency: contact.address},
|
||||
addressSource: AddressSource.contact,
|
||||
handle: contact.name,
|
||||
profileName: contact.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
import 'package:hash_wallet/evm/evm.dart';
|
||||
import 'package:hash_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:hash_wallet/store/app_store.dart';
|
||||
import 'package:hash_wallet/view_model/send/template_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:hash_wallet/entities/template.dart';
|
||||
import 'package:hash_wallet/store/templates/send_template_store.dart';
|
||||
import 'package:hash_wallet/core/template_validator.dart';
|
||||
import 'package:hash_wallet/core/address_validator.dart';
|
||||
import 'package:hash_wallet/core/amount_validator.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:hash_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
|
||||
part 'send_template_view_model.g.dart';
|
||||
|
||||
class SendTemplateViewModel = SendTemplateViewModelBase with _$SendTemplateViewModel;
|
||||
|
||||
abstract class SendTemplateViewModelBase with Store {
|
||||
final WalletBase _wallet;
|
||||
final AppStore _appStore;
|
||||
final SendTemplateStore _sendTemplateStore;
|
||||
final FiatConversionStore _fiatConversationStore;
|
||||
|
||||
SendTemplateViewModelBase(
|
||||
this._wallet, this._appStore, this._sendTemplateStore, this._fiatConversationStore)
|
||||
: recipients = ObservableList<TemplateViewModel>() {
|
||||
addRecipient();
|
||||
}
|
||||
|
||||
ObservableList<TemplateViewModel> recipients;
|
||||
|
||||
@action
|
||||
void addRecipient() {
|
||||
recipients.add(TemplateViewModel(
|
||||
wallet: _wallet, appStore: _appStore, fiatConversationStore: _fiatConversationStore));
|
||||
}
|
||||
|
||||
@action
|
||||
void removeRecipient(TemplateViewModel recipient) {
|
||||
recipients.remove(recipient);
|
||||
}
|
||||
|
||||
AmountValidator get amountValidator => AmountValidator(
|
||||
currency: walletTypeToCryptoCurrency(_wallet.type, chainId: _wallet.chainId),
|
||||
amountParsingProxy: _appStore.amountParsingProxy,
|
||||
);
|
||||
|
||||
AddressValidator get addressValidator =>
|
||||
AddressValidator(type: _wallet.currency, isTestnet: _wallet.isTestnet);
|
||||
|
||||
TemplateValidator get templateValidator => TemplateValidator();
|
||||
|
||||
bool get hasMultiRecipient => !isEVMCompatibleChain(_wallet.type);
|
||||
|
||||
@computed
|
||||
CryptoCurrency get cryptoCurrency => _wallet.currency;
|
||||
|
||||
@computed
|
||||
String get fiatCurrency => _appStore.settingsStore.fiatCurrency.title;
|
||||
|
||||
@computed
|
||||
int get fiatCurrencyDecimals => _appStore.settingsStore.fiatCurrency.decimals;
|
||||
|
||||
@computed
|
||||
ObservableList<Template> get templates => _sendTemplateStore.templates;
|
||||
|
||||
@action
|
||||
void updateTemplate() => _sendTemplateStore.update();
|
||||
|
||||
@action
|
||||
void addTemplate(
|
||||
{required String name,
|
||||
required bool isCurrencySelected,
|
||||
required String cryptoCurrency,
|
||||
required String address,
|
||||
required String amount,
|
||||
required String amountFiat,
|
||||
required List<Template> additionalRecipients}) {
|
||||
_sendTemplateStore.addTemplate(
|
||||
name: name,
|
||||
isCurrencySelected: isCurrencySelected,
|
||||
address: address,
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
fiatCurrency: fiatCurrency,
|
||||
amount: amount,
|
||||
amountFiat: amountFiat,
|
||||
additionalRecipients: additionalRecipients);
|
||||
updateTemplate();
|
||||
}
|
||||
|
||||
@action
|
||||
void removeTemplate({required Template template}) {
|
||||
_sendTemplateStore.remove(template: template);
|
||||
updateTemplate();
|
||||
}
|
||||
|
||||
@computed
|
||||
List<CryptoCurrency> get walletCurrencies => _wallet.balance.keys.toList();
|
||||
|
||||
bool get hasMultipleTokens => isEVMCompatibleChain(_wallet.type);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:hash_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/parsed_address.dart';
|
||||
import 'package:hash_wallet/core/address_resolver/address_resolver_service.dart';
|
||||
import 'package:hash_wallet/core/address_validator.dart';
|
||||
import 'package:hash_wallet/core/amount_parsing_proxy.dart';
|
||||
import 'package:hash_wallet/core/amount_validator.dart';
|
||||
@@ -8,6 +10,7 @@ import 'package:hash_wallet/core/execution_state.dart';
|
||||
import 'package:hash_wallet/core/open_crypto_pay/exceptions.dart';
|
||||
import 'package:hash_wallet/core/open_crypto_pay/models.dart';
|
||||
import 'package:hash_wallet/core/open_crypto_pay/open_cryptopay_service.dart';
|
||||
import 'package:hash_wallet/core/utilities.dart';
|
||||
import 'package:hash_wallet/core/validator.dart';
|
||||
import 'package:hash_wallet/core/wallet_change_listener_view_model.dart';
|
||||
import 'package:hash_wallet/entities/calculate_fiat_amount.dart';
|
||||
@@ -15,7 +18,6 @@ import 'package:hash_wallet/entities/contact.dart';
|
||||
import 'package:hash_wallet/entities/contact_record.dart';
|
||||
import 'package:hash_wallet/entities/evm_transaction_error_fees_handler.dart';
|
||||
import 'package:hash_wallet/entities/fiat_currency.dart';
|
||||
import 'package:hash_wallet/entities/parsed_address.dart';
|
||||
import 'package:hash_wallet/entities/preferences_key.dart';
|
||||
import 'package:hash_wallet/entities/template.dart';
|
||||
import 'package:hash_wallet/entities/transaction_description.dart';
|
||||
@@ -38,16 +40,18 @@ import 'package:hash_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/send/fees_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/send/output.dart';
|
||||
import 'package:hash_wallet/view_model/send/send_template_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/send/send_view_model_state.dart';
|
||||
import 'package:hash_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:hash_wallet/wownero/wownero.dart';
|
||||
import 'package:cw_core/amount/amount_sanitizer.dart';
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/exceptions.dart';
|
||||
import 'package:cw_core/lnurl.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
@@ -85,8 +89,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
|
||||
SendViewModelBase(
|
||||
this._appStore,
|
||||
this.sendTemplateViewModel,
|
||||
this._fiatConversationStore,
|
||||
this._adrResService,
|
||||
this.balanceViewModel,
|
||||
this.contactListViewModel,
|
||||
this.transactionDescriptionBox,
|
||||
@@ -105,11 +109,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
fiatFromSettings = _appStore.settingsStore.fiatCurrency,
|
||||
fiatCurrencies = FiatCurrency.all,
|
||||
super(appStore: _appStore) {
|
||||
outputs.add(Output(wallet, _appStore, _fiatConversationStore, () => selectedCryptoCurrency));
|
||||
outputs.add(Output(wallet, _appStore, _fiatConversationStore, _outputCryptoCurrencyHandler));
|
||||
|
||||
unspentCoinsListViewModel
|
||||
.initialSetup()
|
||||
.then((_) => unspentCoinsListViewModel.resetUnspentCoinsInfoSelections());
|
||||
unspentCoinsListViewModel.initialSetup();
|
||||
// .then((_) => unspentCoinsListViewModel.resetUnspentCoinsInfoSelections());
|
||||
|
||||
reaction((_) {
|
||||
if (isEVMCompatibleChain(wallet.type)) {
|
||||
@@ -119,9 +122,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
return null;
|
||||
}, (_) async {
|
||||
// When chain changes, update currencies and selected currency
|
||||
final selectionAtChainChange = selectedCryptoCurrency;
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
currencies = wallet.balance.keys.toList();
|
||||
selectedCryptoCurrency = wallet.currency;
|
||||
if (selectedCryptoCurrency == selectionAtChainChange) {
|
||||
selectedCryptoCurrency = wallet.currency;
|
||||
}
|
||||
updateSendingBalance();
|
||||
});
|
||||
}
|
||||
@@ -142,18 +148,48 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
|
||||
bool get isEVMWallet => isEVMCompatibleChain(walletType);
|
||||
|
||||
CryptoCurrency _outputCryptoCurrencyHandler([CryptoCurrency? override]) {
|
||||
if (override != null && override != selectedCryptoCurrency) {
|
||||
runInAction(() => selectedCryptoCurrency = override);
|
||||
}
|
||||
|
||||
return selectedCryptoCurrency;
|
||||
}
|
||||
|
||||
@action
|
||||
void setShowAddressBookPopup(bool value) => _settingsStore.showAddressBookPopupEnabled = value;
|
||||
|
||||
@action
|
||||
void addOutput() =>
|
||||
outputs.add(Output(wallet, _appStore, _fiatConversationStore, () => selectedCryptoCurrency));
|
||||
outputs.add(Output(wallet, _appStore, _fiatConversationStore, _outputCryptoCurrencyHandler));
|
||||
|
||||
@action
|
||||
void removeOutput(Output output) {
|
||||
if (isBatchSending) outputs.remove(output);
|
||||
}
|
||||
|
||||
Future<ParsedAddress?> resolveAddressForOutput(Output output) async {
|
||||
final query = output.address.trim();
|
||||
if (query.isEmpty) return null;
|
||||
|
||||
// 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,
|
||||
network: wallet.walletInfo.network,
|
||||
).isValid(query);
|
||||
if (isValidAddress) return null;
|
||||
}
|
||||
|
||||
final results = await _adrResService.resolve(
|
||||
query: query,
|
||||
wallet: wallet,
|
||||
currency: selectedCryptoCurrency,
|
||||
);
|
||||
|
||||
return results.isEmpty ? null : results.first;
|
||||
}
|
||||
|
||||
@action
|
||||
void clearOutputs() {
|
||||
outputs.clear();
|
||||
@@ -264,8 +300,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();
|
||||
|
||||
@@ -274,6 +313,24 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
@observable
|
||||
PendingTransaction? pendingTransaction;
|
||||
|
||||
// Bumped when a preparation is dismissed or replaced.
|
||||
//
|
||||
// Finished call sets pendingTransaction only if it matches this value to
|
||||
// its own value.
|
||||
int _sendPreparationId = 0;
|
||||
|
||||
String? get pendingTransactionAdditionalCostNotice {
|
||||
final additionalCost = pendingTransaction?.additionalCost;
|
||||
|
||||
if (additionalCost == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return S.current.recipient_account_creation_fee(
|
||||
_appStore.amountParsingProxy.asDisplayStringWithSymbol(additionalCost),
|
||||
);
|
||||
}
|
||||
|
||||
@computed
|
||||
String get balance {
|
||||
if (walletType == WalletType.litecoin && coinTypeToSpendFrom == UnspentCoinType.mweb) {
|
||||
@@ -286,10 +343,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
// Handle case where balance might not be available yet (e.g., during chain switch)
|
||||
final balanceForCurrency = wallet.balance[selectedCryptoCurrency];
|
||||
if (balanceForCurrency == null) {
|
||||
return _appStore.amountParsingProxy.getDisplayCryptoString(0, selectedCryptoCurrency);
|
||||
return _appStore.amountParsingProxy.asDisplayString(Money.zero(selectedCryptoCurrency));
|
||||
}
|
||||
return _appStore.amountParsingProxy.getDisplayCryptoStringFromBigInt(
|
||||
wallet.balance[selectedCryptoCurrency]!.fullAvailableBalance, selectedCryptoCurrency);
|
||||
return _appStore.amountParsingProxy
|
||||
.asDisplayString(wallet.balance[selectedCryptoCurrency]!.available);
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -316,7 +373,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
// only for electrum, monero, wownero wallets atm:
|
||||
switch (wallet.type) {
|
||||
case WalletType.bitcoin:
|
||||
if (coinTypeToSpendFrom == UnspentCoinType.lightning) return balance;
|
||||
if (selectedCryptoCurrency == CryptoCurrency.btcln) return balance;
|
||||
return _appStore.amountParsingProxy.getDisplayCryptoString(
|
||||
await unspentCoinsListViewModel.getSendingBalance(coinTypeToSpendFrom),
|
||||
walletTypeToCryptoCurrency(walletType));
|
||||
@@ -350,31 +407,20 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
// If silent payments scanning, can still send payments
|
||||
(wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus);
|
||||
|
||||
bool isSendToSilentPayments(Output output) =>
|
||||
wallet.type == WalletType.bitcoin &&
|
||||
(RegExp(AddressValidator.silentPaymentAddressPatternMainnet).hasMatch(output.address) ||
|
||||
RegExp(AddressValidator.silentPaymentAddressPatternMainnet)
|
||||
.hasMatch(output.extractedAddress) ||
|
||||
(output.parsedAddress.addresses.isNotEmpty &&
|
||||
RegExp(AddressValidator.silentPaymentAddressPatternMainnet)
|
||||
.hasMatch(output.parsedAddress.addresses[0])));
|
||||
bool isSendToSilentPayments(Output output) {
|
||||
if (wallet.type != WalletType.bitcoin) return false;
|
||||
|
||||
final sp = RegExp(AddressValidator.silentPaymentAddressPatternMainnet);
|
||||
|
||||
final address = output.extractedAddress.isNotEmpty ? output.extractedAddress : output.address;
|
||||
|
||||
return sp.hasMatch(address);
|
||||
}
|
||||
|
||||
@computed
|
||||
List<Template> get templates => sendTemplateViewModel.templates
|
||||
.where((template) => _isEqualCurrency(template.cryptoCurrency))
|
||||
.toList();
|
||||
|
||||
@computed
|
||||
bool get hasCoinControl =>
|
||||
[
|
||||
WalletType.bitcoin,
|
||||
WalletType.litecoin,
|
||||
WalletType.monero,
|
||||
WalletType.wownero,
|
||||
WalletType.bitcoinCash,
|
||||
WalletType.dogecoin
|
||||
].contains(wallet.type) &&
|
||||
coinTypeToSpendFrom != UnspentCoinType.lightning;
|
||||
wallet.hasCoinControl && coinTypeToSpendFrom != UnspentCoinType.lightning;
|
||||
|
||||
@computed
|
||||
bool get hasFees => feesViewModel.hasFees && coinTypeToSpendFrom != UnspentCoinType.lightning;
|
||||
@@ -394,6 +440,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
String get selectedCryptoCurrencySymbol =>
|
||||
amountParsingProxy.getCryptoSymbol(selectedCryptoCurrency);
|
||||
|
||||
@computed
|
||||
bool get useBaseUnits => amountParsingProxy.useSatoshi(selectedCryptoCurrency);
|
||||
|
||||
List<CryptoCurrency> currencies;
|
||||
|
||||
WalletType get walletType => wallet.type;
|
||||
@@ -411,20 +460,20 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
|
||||
final AppStore _appStore;
|
||||
SettingsStore get _settingsStore => _appStore.settingsStore;
|
||||
final SendTemplateViewModel sendTemplateViewModel;
|
||||
final BalanceViewModel balanceViewModel;
|
||||
final ContactListViewModel contactListViewModel;
|
||||
final HardwareWalletViewModel? hardwareWalletViewModel;
|
||||
final FeesViewModel feesViewModel;
|
||||
final FiatConversionStore _fiatConversationStore;
|
||||
final AddressResolverService _adrResService;
|
||||
final Box<TransactionDescription> transactionDescriptionBox;
|
||||
|
||||
@computed
|
||||
AmountParsingProxy get amountParsingProxy => _appStore.amountParsingProxy;
|
||||
|
||||
@computed
|
||||
bool get hasMultiRecipient =>
|
||||
sendTemplateViewModel.hasMultiRecipient && coinTypeToSpendFrom != UnspentCoinType.lightning;
|
||||
!isEVMCompatibleChain(wallet.type) &&
|
||||
coinTypeToSpendFrom != UnspentCoinType.lightning;
|
||||
|
||||
@computed
|
||||
String get languageCode => _appStore.settingsStore.languageCode;
|
||||
@@ -464,6 +513,19 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
bool get shouldDisplayTOTP2FAForSendsToInternalWallet =>
|
||||
_settingsStore.shouldRequireTOTP2FAForSendsToInternalWallets;
|
||||
|
||||
@computed
|
||||
TransactionInfo? get transactionInfo {
|
||||
if (isEVMCompatibleChain(walletType)) {
|
||||
return wallet.transactionHistory.transactions[pendingTransaction?.evmTxHashFromRawHex];
|
||||
}
|
||||
if (walletType == WalletType.monero) {
|
||||
// monero tx history keys are in format txhash_amount_accountindex_addressindex
|
||||
return wallet.transactionHistory.transactions[wallet.transactionHistory.transactions.keys
|
||||
.firstWhereOrNull((item) => item.split("_").first == pendingTransaction?.id)];
|
||||
}
|
||||
return wallet.transactionHistory.transactions[pendingTransaction?.id];
|
||||
}
|
||||
|
||||
//* Still open to further optimize these checks
|
||||
//* It works but can be made better
|
||||
@action
|
||||
@@ -498,6 +560,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
|
||||
@action
|
||||
Future<void> dismissTransaction() async {
|
||||
_sendPreparationId++;
|
||||
_ledgerTxStateTimer?.cancel();
|
||||
_ledgerTxStateTimer = null;
|
||||
state = InitialExecutionState();
|
||||
if (ocpRequest != null) {
|
||||
clearOutputs();
|
||||
@@ -550,8 +615,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
clearOutputs();
|
||||
|
||||
outputs.first.address = paymentRequest.address;
|
||||
outputs.first.parsedAddress =
|
||||
ParsedAddress(addresses: [paymentRequest.address], name: ocpRequest!.receiverName);
|
||||
outputs.first.parsedAddress = ParsedAddress(
|
||||
parsedAddressByCurrencyMap: {currency: paymentRequest.address},
|
||||
handle: ocpRequest!.receiverName);
|
||||
outputs.first.setCryptoAmount(paymentRequest.amount);
|
||||
outputs.first.note = ocpRequest!.receiverName;
|
||||
|
||||
@@ -587,20 +653,38 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
|
||||
@action
|
||||
Future<PendingTransaction?> createTransaction({ExchangeProvider? provider, Trade? trade}) async {
|
||||
final preparationId = ++_sendPreparationId;
|
||||
_ledgerTxStateTimer?.cancel();
|
||||
_ledgerTxStateTimer = null;
|
||||
pendingTransaction = null;
|
||||
Timer? preparationTimer;
|
||||
|
||||
bool replaced() {
|
||||
if (preparationId == _sendPreparationId) {
|
||||
return false;
|
||||
}
|
||||
preparationTimer?.cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!(state is IsExecutingState)) state = IsExecutingState();
|
||||
|
||||
if (wallet.isHardwareWallet) {
|
||||
state = IsAwaitingDeviceResponseState();
|
||||
if (walletType == WalletType.monero) {
|
||||
_ledgerTxStateTimer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
preparationTimer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
if (preparationId != _sendPreparationId) {
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
if (monero!.getLastLedgerCommand() == "INS_CLSAG") {
|
||||
timer.cancel();
|
||||
state = IsDeviceSigningResponseState();
|
||||
}
|
||||
});
|
||||
_ledgerTxStateTimer = preparationTimer;
|
||||
} else {
|
||||
state = IsAwaitingDeviceResponseState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,19 +693,32 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
final isSendAll = outputs.any((output) => output.sendAll);
|
||||
|
||||
if (!isSendAll) {
|
||||
final estimateTxAmountDouble = outputs.fold<double>(
|
||||
0, (acc, output) => acc + (double.tryParse(output.cryptoAmount) ?? 0));
|
||||
if (estimateTxAmountDouble <= 0) throw Exception('Amount must be greater than 0');
|
||||
final estimateTxAmount = outputs.fold<BigInt>(
|
||||
BigInt.zero, (acc, output) => acc + output.cryptoAmountMoney.amount);
|
||||
if (estimateTxAmount <= BigInt.zero) {
|
||||
throw Exception('Amount must be greater than 0');
|
||||
}
|
||||
}
|
||||
|
||||
pendingTransaction = await wallet.createTransaction(_credentials(provider));
|
||||
final prepared = await wallet.createTransaction(_credentials(provider));
|
||||
if (replaced()) {
|
||||
return null;
|
||||
}
|
||||
pendingTransaction = prepared;
|
||||
|
||||
final txAmountDouble = double.tryParse(pendingTransaction?.amountFormatted ?? '0') ?? 0.0;
|
||||
final bool isTradeTx = trade != null && provider != null;
|
||||
|
||||
if (isTradeTx) {
|
||||
final tradeAmountDouble = double.tryParse(trade.amount) ?? 0.0;
|
||||
if (tradeAmountDouble <= 0) throw Exception('Trade amount must be greater than 0');
|
||||
final tradeAmountMoney = Money.tryParse(
|
||||
trade.amount.sanitized(),
|
||||
trade.from ?? selectedCryptoCurrency,
|
||||
strictParsing: false,
|
||||
);
|
||||
if (tradeAmountMoney == null || tradeAmountMoney.sign <= 0) {
|
||||
throw Exception('Trade amount must be greater than 0');
|
||||
}
|
||||
final tradeAmountDouble = double.tryParse(tradeAmountMoney.toString()) ?? 0.0;
|
||||
|
||||
if (trade.isSendAll == true) {
|
||||
if (provider is NearIntentsExchangeProvider) {
|
||||
@@ -644,6 +741,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
state = ExecutedSuccessfullyState();
|
||||
return pendingTransaction;
|
||||
} catch (e) {
|
||||
if (replaced()) {
|
||||
return null;
|
||||
}
|
||||
_ledgerTxStateTimer?.cancel();
|
||||
// if (e is LedgerException) {
|
||||
// final errorCode = e.errorCode.toRadixString(16);
|
||||
@@ -721,7 +821,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
}
|
||||
|
||||
try {
|
||||
state = TransactionCommitting();
|
||||
state = wallet.isHardwareWallet && walletType == WalletType.monero
|
||||
? IsAwaitingDeviceResponseState()
|
||||
: TransactionCommitting();
|
||||
|
||||
if (ocpRequest != null) {
|
||||
await _handleOcpRequest();
|
||||
@@ -747,6 +849,28 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
});
|
||||
}
|
||||
|
||||
// FIXME(malik) ideally, this should be done wallet-side.
|
||||
// it is required because evm, solana and tron don't actually save the transaction info when you send something.
|
||||
// instead, they rely on the tx to eventually get fetched at sync time, which can take a while
|
||||
if (isEVMWallet) {
|
||||
final selectedToken = evm!.getERC20Currencies(wallet).firstWhereOrNull(
|
||||
(token) => token.title.toUpperCase() == selectedCryptoCurrency.title.toUpperCase(),
|
||||
);
|
||||
wallet.transactionHistory.addOne(evm!.getTransactionInfo(
|
||||
id: pendingTransaction!.evmTxHashFromRawHex!,
|
||||
height: 0,
|
||||
amount: outputs.first.cryptoAmountMoney,
|
||||
fee: pendingTransaction!.fee,
|
||||
tokenSymbol: selectedCryptoCurrency.title,
|
||||
direction: TransactionDirection.outgoing,
|
||||
isPending: true,
|
||||
date: DateTime.now(),
|
||||
confirmations: 0,
|
||||
chainId: wallet.chainId ?? 0,
|
||||
contractAddress: selectedToken?.contractAddress,
|
||||
));
|
||||
}
|
||||
|
||||
if (pendingTransaction!.id.isNotEmpty) {
|
||||
_addTransactionDescription();
|
||||
}
|
||||
@@ -763,9 +887,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
|
||||
Future<void> _addTransactionDescription() async {
|
||||
String address = outputs.fold('', (acc, value) {
|
||||
return value.isParsedAddress
|
||||
? '$acc${value.address}\n${value.extractedAddress}\n\n'
|
||||
: '$acc${value.address}\n\n';
|
||||
final canonical = value.extractedAddress.trim().isNotEmpty
|
||||
? value.extractedAddress.trim()
|
||||
: value.address.trim();
|
||||
if (canonical.isEmpty) {
|
||||
return acc;
|
||||
}
|
||||
return '$acc$canonical\n\n';
|
||||
});
|
||||
|
||||
address = address.trim();
|
||||
@@ -891,8 +1019,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
..addAll(contactListViewModel.walletContacts.map((contact) => contact.address));
|
||||
|
||||
for (final output in outputs) {
|
||||
final address =
|
||||
output.isParsedAddress ? output.parsedAddress.addresses.first : output.address;
|
||||
final address = output.isParsedAddress
|
||||
? output.parsedAddress.parsedAddressByCurrencyMap[selectedCryptoCurrency] ?? ''
|
||||
: output.address;
|
||||
|
||||
if (address.isNotEmpty &&
|
||||
!contactAddresses.contains(address) &&
|
||||
@@ -1035,9 +1164,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
String? payjoinUri;
|
||||
|
||||
@action
|
||||
Future<void> fetchTokenForContractAddress(String contractAddress) async {
|
||||
Future<void> fetchTokenForContractAddress(String contractAddress,
|
||||
{WalletType? walletType}) async {
|
||||
final token = await TokenUtilities.findTokenByAddress(
|
||||
walletType: wallet.type,
|
||||
walletType: walletType ?? wallet.type,
|
||||
address: contractAddress,
|
||||
);
|
||||
|
||||
@@ -1045,4 +1175,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
selectedCryptoCurrency = token;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void applyAnyPayCurrency(CryptoCurrency currency) {
|
||||
selectedCryptoCurrency = currency;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import 'package:hash_wallet/entities/template.dart';
|
||||
import 'package:hash_wallet/store/app_store.dart';
|
||||
import 'package:hash_wallet/view_model/send/output.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:hash_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
|
||||
part 'template_view_model.g.dart';
|
||||
|
||||
class TemplateViewModel = TemplateViewModelBase with _$TemplateViewModel;
|
||||
|
||||
abstract class TemplateViewModelBase with Store {
|
||||
final WalletBase _wallet;
|
||||
final AppStore _appStore;
|
||||
final FiatConversionStore _fiatConversationStore;
|
||||
|
||||
TemplateViewModelBase({
|
||||
required WalletBase wallet,
|
||||
required AppStore appStore,
|
||||
required FiatConversionStore fiatConversationStore,
|
||||
}) : _wallet = wallet,
|
||||
_appStore = appStore,
|
||||
_fiatConversationStore = fiatConversationStore,
|
||||
_currency = wallet.currency,
|
||||
output = Output(wallet, appStore, fiatConversationStore, () => wallet.currency) {
|
||||
output = Output(_wallet, _appStore, _fiatConversationStore, () => _currency);
|
||||
}
|
||||
|
||||
@observable
|
||||
Output output;
|
||||
|
||||
@observable
|
||||
String name = '';
|
||||
|
||||
@observable
|
||||
String address = '';
|
||||
|
||||
@observable
|
||||
CryptoCurrency _currency;
|
||||
|
||||
@observable
|
||||
bool isCryptoSelected = true;
|
||||
|
||||
@action
|
||||
void setCryptoCurrency(bool value) => isCryptoSelected = value;
|
||||
|
||||
@action
|
||||
void reset() {
|
||||
name = '';
|
||||
address = '';
|
||||
isCryptoSelected = true;
|
||||
output.reset();
|
||||
}
|
||||
|
||||
Template toTemplate({required String cryptoCurrency, required String fiatCurrency}) {
|
||||
return Template(
|
||||
isCurrencySelectedRaw: isCryptoSelected,
|
||||
nameRaw: name,
|
||||
addressRaw: address,
|
||||
cryptoCurrencyRaw: cryptoCurrency,
|
||||
fiatCurrencyRaw: fiatCurrency,
|
||||
amountRaw: output.cryptoAmount,
|
||||
amountFiatRaw: output.fiatAmount);
|
||||
}
|
||||
|
||||
@action
|
||||
void changeSelectedCurrency(CryptoCurrency currency) {
|
||||
isCryptoSelected = true;
|
||||
_currency = currency;
|
||||
}
|
||||
|
||||
@computed
|
||||
CryptoCurrency get selectedCurrency => _currency;
|
||||
}
|
||||
Reference in New Issue
Block a user