cake_wallet/lib/view_model/dashboard/balance_view_model.dart
Omar Hatem 36eacd8698
Wownero (#1485)
* fix: scanning issues

* fix: sync, storing silent unspents

* chore: deps

* fix: label issues, clear spent utxo

* chore: deps

* fix: build

* fix: missing types

* feat: new electrs API & changes, fixes for last block scanning

* feat: Scan Silent Payments homepage toggle

* chore: build configure

* feat: generic fixes, testnet UI improvements, useSSL on bitcoin nodes

* fix: invalid Object in sendData

* feat: improve addresses page & address book displays

* feat: silent payments labeled addresses disclaimer

* fix: missing i18n

* chore: print

* feat: single block scan, rescan by date working for btc mainnet

* feat: new cake features page replace market page, move sp scan toggle, auto switch node pop up alert

* feat: delete silent addresses

* fix: red dot in non ssl nodes

* fix: inconsistent connection states, fix tx history

* fix: tx & balance displays, cpfp sending

* feat: new rust lib

* chore: node path

* fix: check node based on network

* fix: missing txcount from addresses

* style: padding in feature page cards

* fix: restore not getting all wallet addresses by type

* fix: auto switch node broken

* fix: silent payment txs not being restored

* feat: change scanning to subscription model, sync improvements

* fix: scan re-subscription

* fix: default nodes

* fix: improve scanning by date, fix single block scan

* refactor: common function for input tx selection

* various fixes for build issues

* initial monero.dart implementation

* ...

* multiple wallets
new lib
minor fixes

* other fixes from monero.dart and monero_c

* fix: nodes & build

* update build scripts
fix polyseed

* remove unnecessary code

* Add windows app, build scripts and build guide for it.

* Minor fix in generated monero configs

* fix: send all with multiple outs

* add missing monero_c command

* add android build script

* Merge and fix main

* undo android ndk removal

* Fix modified exception_handler.dart

* Temporarily remove haven

* fix build issues

* fix pr script

* Fixes for build monero.dart (monero_c) for windows.

* monero build script

* wip: ios build script

* refactor: unchanged file

* Added build guides for iOS and macOS. Replaced nproc call on macOS. Added macOS configuration for configure_cake_wallet.sh script.

* Update monero.dart and monero_c versions.

* Add missed windows build scripts

* Update the application configuration for windows build script.

* Update cw_monero pubspec lock file for monero.dart

* Update pr_test_build.yml

* chore: upgrade

* chore: merge changes

* refactor: unchanged files [skip ci]

* Fix conflicts with main

* fix for multiple wallets

* Add tron to windows application configuration.

* Add macOS option for description message in configure_cake_wallet.sh

* Include missed monero dll for windows.

* fix conflicts with main

* Disable haven configuration for iOS as default. Add ability to configure cakewallet for iOS with  for configuration script. Remove cw_shared configuration for cw_monero.

* fix: scan fixes, add date, allow sending while scanning

* add missing nano secrets file [skip ci]

* ios library

* don't pull prebuilds android

* Add auto generation of manifest file for android project even for iOS, macOS, Windows.

* feat: sync fixes, sp settings

* feat: fix resyncing

* store crash fix

* make init async so it won't lag
disable print starts

* fix monero_c build issues

* libstdc++

* Fix MacOS saving wallet file issue
Fix Secure Storage issue (somehow)

* update pubspec.lock

* fix build script

* Use dylib as iOS framework. Use custom path for loading of iOS framework for monero.dart. Add script for generate iOS framework for monero wallet.

* fix: date from height logic, status disconnected & chain tip get

* fix: params

* feat: electrum migration if using cake electrum

* fix nodes
update versions

* re-enable tron

* update sp_scanner to work on iOS [skip ci]

* bump monero_c hash

* bump monero_c commit

* bump moneroc version

* bump monero_c commit

* Add ability to build monero wallet lib as universal lib. Update macOS build guide. Change default arch for macOS project to .

* fix: wrong socket for old electrum nodes

* Fix unchecked wallet type call

* get App Dir correctly in default_settings_migration.dart

* handle previous issue with fetching linux documents directory [skip ci]

* backup fix

* fix NTFS issues

* Close the wallet when the wallet gets changed

* fix: double balance

* feat: node domain

* fix: menu name

* bump monero_c commit

* fix: update tip on set scanning

* fix: connection switching back and forth

* feat: check if node is electrs, and supports sp

* chore: fix build

* minor enhancements

* fixes and enhancements

* solve conflicts with main

* Only stop wallet on rename and delete

* fix: status toggle

* minor enhancement

* Monero.com fixes

* bump monero_c commit

* update sp_scanner to include windows and linux

* Update macOS build guide. Change brew dependencies for build unbound locally.

* fix conflicts and update macos build guide

* remove build cache when on gh actions

* update secure storage

* free up even more storage

* free up more storage

* Add initial wownero

* fix conflicts

* fix workflow issue

* build wownero

* ios and windows changes

* macos

* complete wownero flow (app side)

* add keychain group entitlement and update script for RunnerBase on macos

* update secure_storage version to 8.1.0 in configure.dart

* add wownero framework

* update ios builds

* proper path for wownero and monero

* finalizing wownero

* finalizing wownero

* free up even more storage

* revert commenting of build gradle configs

* revert commenting of secrets [skip ci]

* free more storage

* minor fixes

* link android wownero libraries

* bump monero_c commit

* wownero fixes

* rename target

* build_single.sh using clean env

* bump monero_c commit

* minor fix

* Add wownero polyseed

* fix conflicts with main

* fix: wallet seed display
fix: wownero not refreshing

* fix: wallet seed display
fix: wownero not refreshing

* bump monero_c commit

* minor fixes

* fix: incorrectly displaying XMR instead of WOW

* fix: incorrect restore height in wownero

* bump monero_c commit

* Add Inno Setup Script for windows exe installer

* drop libc++_shared.so

* fixes from comments

* Fix CMake for windows

* Merge latest monero dart changes [skip ci]

* bump monero_c commit

* add wownero to build scripts for macos [skip ci]

* add 14 word seed support to wownero

* UI fixes for wownero seed restore

* minor fixes

* reformat code to pass lints

* wownero: fixes
haven: removal popup

* minor iOS fix [skip ci]

* fix: wownero confirmation count (it is spendable after 3 confirms)
fix: transaction history not displaying in WOW and XMR
when tx has 0 confirms,
This is more of a workaround, because I have no idea
why would the cpp code not return pending transaction.

* Update preferences_key.dart [skip ci]

* minor fixes

---------

Co-authored-by: Rafael Saes <git@rafael.saes.dev>
Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
Co-authored-by: M <m@cakewallet.com>
Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
Co-authored-by: Matthew Fosse <matt@fosse.co>
2024-07-04 22:43:17 +03:00

424 lines
12 KiB
Dart

import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:mobx/mobx.dart';
part 'balance_view_model.g.dart';
class BalanceRecord {
const BalanceRecord(
{required this.availableBalance,
required this.additionalBalance,
required this.frozenBalance,
required this.fiatAvailableBalance,
required this.fiatAdditionalBalance,
required this.fiatFrozenBalance,
required this.asset,
required this.formattedAssetTitle});
final String fiatAdditionalBalance;
final String fiatAvailableBalance;
final String fiatFrozenBalance;
final String additionalBalance;
final String availableBalance;
final String frozenBalance;
final CryptoCurrency asset;
final String formattedAssetTitle;
}
class BalanceViewModel = BalanceViewModelBase with _$BalanceViewModel;
abstract class BalanceViewModelBase with Store {
BalanceViewModelBase(
{required this.appStore, required this.settingsStore, required this.fiatConvertationStore})
: isReversing = false,
isShowCard = appStore.wallet!.walletInfo.isShowIntroCakePayCard,
wallet = appStore.wallet! {
reaction((_) => appStore.wallet, _onWalletChange);
}
final AppStore appStore;
final SettingsStore settingsStore;
final FiatConversionStore fiatConvertationStore;
bool get canReverse => false;
@observable
bool isReversing;
@observable
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
@computed
bool get hasSilentPayments => wallet.type == WalletType.bitcoin;
@computed
double get price {
final price = fiatConvertationStore.prices[appStore.wallet!.currency];
if (price == null) {
// price should update on next fetch:
return 0;
}
return price;
}
@computed
BalanceDisplayMode get savedDisplayMode => settingsStore.balanceDisplayMode;
@computed
bool get isFiatDisabled => settingsStore.fiatApiMode == FiatApiMode.disabled;
@computed
bool get isHomeScreenSettingsEnabled =>
isEVMCompatibleChain(wallet.type) ||
wallet.type == WalletType.solana ||
wallet.type == WalletType.tron;
@computed
bool get hasAccounts => wallet.type == WalletType.monero;
@computed
SortBalanceBy get sortBalanceBy => settingsStore.sortBalanceBy;
@computed
bool get pinNativeToken => settingsStore.pinNativeTokenAtTop;
@computed
String get asset {
final typeFormatted = walletTypeToString(appStore.wallet!.type);
switch (wallet.type) {
case WalletType.haven:
return '$typeFormatted Assets';
default:
return typeFormatted;
}
}
@computed
BalanceDisplayMode get displayMode {
if (isReversing) {
if (savedDisplayMode == BalanceDisplayMode.hiddenBalance) {
return BalanceDisplayMode.displayableBalance;
} else {
return BalanceDisplayMode.hiddenBalance;
}
}
return savedDisplayMode;
}
@computed
String get availableBalanceLabel {
switch (wallet.type) {
case WalletType.monero:
case WalletType.wownero:
case WalletType.haven:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.nano:
case WalletType.banano:
case WalletType.solana:
case WalletType.tron:
return S.current.xmr_available_balance;
default:
return S.current.confirmed;
}
}
@computed
String get additionalBalanceLabel {
switch (wallet.type) {
case WalletType.monero:
case WalletType.wownero:
case WalletType.haven:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
return S.current.xmr_full_balance;
case WalletType.nano:
case WalletType.banano:
return S.current.receivable_balance;
default:
return S.current.unconfirmed;
}
}
@computed
bool get hasMultiBalance => appStore.wallet!.type == WalletType.haven;
@computed
String get availableBalance {
final walletBalance = _walletBalance;
if (displayMode == BalanceDisplayMode.hiddenBalance) {
return '---';
}
return walletBalance.formattedAvailableBalance;
}
@computed
String get frozenBalance {
final walletBalance = _walletBalance;
if (displayMode == BalanceDisplayMode.hiddenBalance) {
return '---';
}
return getFormattedFrozenBalance(walletBalance);
}
@computed
String get frozenFiatBalance {
final walletBalance = _walletBalance;
final fiatCurrency = settingsStore.fiatCurrency;
if (displayMode == BalanceDisplayMode.hiddenBalance) {
return '---';
}
return _getFiatBalance(price: price, cryptoAmount: getFormattedFrozenBalance(walletBalance)) +
' ${fiatCurrency.toString()}';
}
@computed
String get additionalBalance {
final walletBalance = _walletBalance;
if (displayMode == BalanceDisplayMode.hiddenBalance) {
return '---';
}
return walletBalance.formattedAdditionalBalance;
}
@computed
String get availableFiatBalance {
final walletBalance = _walletBalance;
final fiatCurrency = settingsStore.fiatCurrency;
if (displayMode == BalanceDisplayMode.hiddenBalance) {
return '---';
}
return _getFiatBalance(price: price, cryptoAmount: walletBalance.formattedAvailableBalance) +
' ${fiatCurrency.toString()}';
}
@computed
String get additionalFiatBalance {
final walletBalance = _walletBalance;
final fiatCurrency = settingsStore.fiatCurrency;
if (displayMode == BalanceDisplayMode.hiddenBalance) {
return '---';
}
return _getFiatBalance(price: price, cryptoAmount: walletBalance.formattedAdditionalBalance) +
' ${fiatCurrency.toString()}';
}
@computed
Map<CryptoCurrency, BalanceRecord> get balances {
return wallet.balance.map((key, value) {
if (displayMode == BalanceDisplayMode.hiddenBalance) {
return MapEntry(
key,
BalanceRecord(
availableBalance: '---',
additionalBalance: '---',
frozenBalance: '---',
fiatAdditionalBalance: isFiatDisabled ? '' : '---',
fiatAvailableBalance: isFiatDisabled ? '' : '---',
fiatFrozenBalance: isFiatDisabled ? '' : '---',
asset: key,
formattedAssetTitle: _formatterAsset(key)));
}
final fiatCurrency = settingsStore.fiatCurrency;
final price = fiatConvertationStore.prices[key] ?? 0;
// if (price == null) {
// throw Exception('Price is null for: $key');
// }
final additionalFiatBalance = isFiatDisabled
? ''
: (fiatCurrency.toString() +
' ' +
_getFiatBalance(price: price, cryptoAmount: value.formattedAdditionalBalance));
final availableFiatBalance = isFiatDisabled
? ''
: (fiatCurrency.toString() +
' ' +
_getFiatBalance(price: price, cryptoAmount: value.formattedAvailableBalance));
final frozenFiatBalance = isFiatDisabled
? ''
: (fiatCurrency.toString() +
' ' +
_getFiatBalance(price: price, cryptoAmount: getFormattedFrozenBalance(value)));
return MapEntry(
key,
BalanceRecord(
availableBalance: value.formattedAvailableBalance,
additionalBalance: value.formattedAdditionalBalance,
frozenBalance: getFormattedFrozenBalance(value),
fiatAdditionalBalance: additionalFiatBalance,
fiatAvailableBalance: availableFiatBalance,
fiatFrozenBalance: frozenFiatBalance,
asset: key,
formattedAssetTitle: _formatterAsset(key)));
});
}
@computed
bool get hasAdditionalBalance => _hasAdditionBalanceForWalletType(wallet.type);
bool _hasAdditionBalanceForWalletType(WalletType type) {
switch (type) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
return false;
default:
return true;
}
}
@computed
List<BalanceRecord> get formattedBalances {
final balance = balances.values.toList();
balance.sort((BalanceRecord a, BalanceRecord b) {
if (wallet.currency == CryptoCurrency.xhv) {
if (b.asset == CryptoCurrency.xhv) {
return 1;
}
if (b.asset == CryptoCurrency.xusd) {
if (a.asset == CryptoCurrency.xhv) {
return -1;
}
return 1;
}
if (b.asset == CryptoCurrency.xbtc) {
return 1;
}
if (b.asset == CryptoCurrency.xeur) {
return 1;
}
return 0;
}
if (pinNativeToken) {
if (b.asset == wallet.currency) return 1;
if (a.asset == wallet.currency) return -1;
}
switch (sortBalanceBy) {
case SortBalanceBy.FiatBalance:
final aFiatBalance = _getFiatBalance(
price: fiatConvertationStore.prices[a.asset] ?? 0, cryptoAmount: a.availableBalance);
final bFiatBalance = _getFiatBalance(
price: fiatConvertationStore.prices[b.asset] ?? 0, cryptoAmount: b.availableBalance);
return (double.tryParse(bFiatBalance) ?? 0)
.compareTo((double.tryParse(aFiatBalance)) ?? 0);
case SortBalanceBy.GrossBalance:
return (double.tryParse(b.availableBalance) ?? 0)
.compareTo(double.tryParse(a.availableBalance) ?? 0);
case SortBalanceBy.Alphabetical:
return a.asset.title.compareTo(b.asset.title);
}
});
return balance;
}
@computed
Balance get _walletBalance {
final balance = wallet.balance[wallet.currency];
if (balance == null) {
throw Exception('No balance for ${wallet.currency}');
}
return balance;
}
@computed
CryptoCurrency get currency => appStore.wallet!.currency;
@observable
bool isShowCard;
ReactionDisposer? _onCurrentWalletChangeReaction;
@action
void _onWalletChange(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>? wallet) {
if (wallet == null) {
return;
}
this.wallet = wallet;
_onCurrentWalletChangeReaction?.reaction.dispose();
isShowCard = wallet.walletInfo.isShowIntroCakePayCard;
}
@action
Future<void> disableIntroCakePayCard() async {
const cardDisplayStatus = false;
wallet.walletInfo.showIntroCakePayCard = cardDisplayStatus;
await wallet.walletInfo.save();
isShowCard = cardDisplayStatus;
}
String _getFiatBalance({required double price, String? cryptoAmount}) {
if (cryptoAmount == null || cryptoAmount.isEmpty || double.tryParse(cryptoAmount) == null) {
return '0.00';
}
return calculateFiatAmount(price: price, cryptoAmount: cryptoAmount);
}
String _formatterAsset(CryptoCurrency asset) {
switch (wallet.type) {
case WalletType.haven:
final assetStringified = asset.toString();
if (asset != CryptoCurrency.xhv && assetStringified[0].toUpperCase() == 'X') {
return assetStringified.replaceFirst('X', 'x');
}
return asset.toString();
default:
return asset.toString();
}
}
String getFormattedFrozenBalance(Balance walletBalance) =>
walletBalance.formattedUnAvailableBalance;
}